diff --git a/.github/workflows/cleanup-pr-tarballs.yml b/.github/workflows/cleanup-pr-tarballs.yml index 1892f3a17..10a35be2c 100644 --- a/.github/workflows/cleanup-pr-tarballs.yml +++ b/.github/workflows/cleanup-pr-tarballs.yml @@ -14,10 +14,16 @@ jobs: runs-on: ubuntu-latest steps: + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} - uses: actions/checkout@v6 - name: Delete PR tarball releases older than 7 days env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | CUTOFF=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-7d +%Y-%m-%dT%H:%M:%SZ) diff --git a/.github/workflows/pr-tarball.yml b/.github/workflows/pr-tarball.yml index a901a2a6e..269530868 100644 --- a/.github/workflows/pr-tarball.yml +++ b/.github/workflows/pr-tarball.yml @@ -50,10 +50,16 @@ jobs: run: | TARBALL_NAME=$(ls *.tgz | head -1 | xargs basename) echo "name=$TARBALL_NAME" >> $GITHUB_OUTPUT + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} - name: Create or update PR release id: release env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} PR_NUMBER: ${{ github.event.pull_request.number }} TARBALL_NAME: ${{ steps.tarball.outputs.name }} run: | diff --git a/.github/workflows/release-main-and-preview.yml b/.github/workflows/release-main-and-preview.yml index b3cfb5ee2..c726518f2 100644 --- a/.github/workflows/release-main-and-preview.yml +++ b/.github/workflows/release-main-and-preview.yml @@ -135,9 +135,16 @@ jobs: - name: Update snapshots run: npm run test:update-snapshots + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Create release branch and PR env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} NEW_VERSION: ${{ steps.bump.outputs.version }} run: | BRANCH_NAME="release/v$NEW_VERSION" @@ -219,9 +226,16 @@ jobs: - name: Update snapshots run: npm run test:update-snapshots + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Create release branch and PR env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} NEW_VERSION: ${{ steps.bump.outputs.version }} run: | BRANCH_NAME="release/v$NEW_VERSION" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 394e9460a..8a0713162 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -160,9 +160,16 @@ jobs: exit 1 fi + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Create Pull Request env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} NEW_VERSION: ${{ steps.bump.outputs.version }} BASE_BRANCH: ${{ steps.release-meta.outputs.base_branch }} DIST_TAG: ${{ steps.release-meta.outputs.dist_tag }} diff --git a/.github/workflows/strands-command.yml b/.github/workflows/strands-command.yml index a4e20a16b..dcf768206 100644 --- a/.github/workflows/strands-command.yml +++ b/.github/workflows/strands-command.yml @@ -94,6 +94,12 @@ jobs: }; await processInputs(context, github, core, inputs); + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} - name: Run Strands Agent uses: ./.github/actions/strands-action with: @@ -104,7 +110,7 @@ jobs: tools: 'strands_tools:shell,retrieve' aws_role_arn: ${{ secrets.AWS_ROLE_ARN }} aws_region: 'us-west-2' - pat_token: ${{ secrets.GITHUB_TOKEN }} + pat_token: ${{ steps.app-token.outputs.token }} env: SESSION_ID: ${{ steps.process-inputs.outputs.session_id }} S3_SESSION_BUCKET: ${{ secrets.AGENT_SESSIONS_BUCKET }} diff --git a/.github/workflows/sync-from-public.yml b/.github/workflows/sync-from-public.yml index b0b6ed106..5d2dbae24 100644 --- a/.github/workflows/sync-from-public.yml +++ b/.github/workflows/sync-from-public.yml @@ -13,10 +13,24 @@ jobs: sync: runs-on: ubuntu-latest steps: +<<<<<<< HEAD - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} +======= + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} +>>>>>>> origin/main - name: Configure Git run: | @@ -101,15 +115,33 @@ jobs: --head "$conflict_branch" || echo "⚠️ Failed to create PR" fi env: +<<<<<<< HEAD GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} +======= + GH_TOKEN: ${{ steps.app-token.outputs.token }} +>>>>>>> origin/main sync-preview: runs-on: ubuntu-latest steps: +<<<<<<< HEAD - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} +======= + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} +>>>>>>> origin/main - name: Configure Git run: | @@ -194,4 +226,8 @@ jobs: --head "$conflict_branch" || echo "⚠️ Failed to create PR" fi env: +<<<<<<< HEAD GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} +======= + GH_TOKEN: ${{ steps.app-token.outputs.token }} +>>>>>>> origin/main diff --git a/.github/workflows/sync-preview.yml b/.github/workflows/sync-preview.yml index 61abcbc86..b4f0fa1b0 100644 --- a/.github/workflows/sync-preview.yml +++ b/.github/workflows/sync-preview.yml @@ -17,19 +17,27 @@ jobs: name: Merge main into preview runs-on: ubuntu-latest steps: + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Checkout preview uses: actions/checkout@v6 with: ref: preview fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} - name: Configure git run: | git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" - - name: Merge main into preview - id: merge + - name: Check if sync needed + id: check run: | git fetch origin main MAIN_SHA=$(git rev-parse origin/main) @@ -37,73 +45,133 @@ jobs: if [[ "$MAIN_SHA" == "$MERGE_BASE" ]]; then echo "✅ preview already contains all of main" - echo "status=up-to-date" >> $GITHUB_OUTPUT - exit 0 + echo "needed=false" >> $GITHUB_OUTPUT + else + echo "needed=true" >> $GITHUB_OUTPUT fi - echo "ℹ️ Merging main into preview..." + - name: Skip if already synced + if: steps.check.outputs.needed == 'false' + run: echo "Nothing to sync." + + - name: Merge main into preview + if: steps.check.outputs.needed == 'true' + id: merge + run: | + # Save preview's version before merge so we can restore it after + PREVIEW_VERSION=$(node -p "require('./package.json').version") + echo "preview_version=$PREVIEW_VERSION" >> $GITHUB_OUTPUT if git merge origin/main --no-edit -m "chore: merge main into preview"; then - git push origin preview - echo "✅ main merged into preview and pushed" - echo "status=merged" >> $GITHUB_OUTPUT + echo "status=clean" >> $GITHUB_OUTPUT else - git merge --abort - echo "status=conflict" >> $GITHUB_OUTPUT + # preview carries a higher version string than main (e.g. 1.0.0-preview.X vs 0.13.X). + # This means package.json/package-lock.json almost always conflict on the version field. + # Accept main's content here; the version is restored in the next step. + for f in package.json package-lock.json; do + if git diff --name-only --diff-filter=U | grep -qx "$f"; then + git checkout --theirs "$f" + git add "$f" + echo " ↳ resolved $f conflict (accepted main, will restore version)" + fi + done + + # Check if all conflicts are now resolved + if [[ -z "$(git diff --name-only --diff-filter=U)" ]]; then + git commit --no-edit -m "chore: merge main into preview" + echo "status=clean" >> $GITHUB_OUTPUT + else + echo "status=conflict" >> $GITHUB_OUTPUT + fi fi - - name: Get original commit author - if: steps.merge.outputs.status == 'conflict' - id: author + - name: Restore preview-owned files + if: steps.merge.outputs.status == 'clean' run: | - AUTHOR=$(git log origin/main -1 --format='%an') - GH_USER=$(git log origin/main -1 --format='%ae' | grep -oP '.*(?=@users\.noreply\.github\.com)' || echo "") - if [[ -z "$GH_USER" ]]; then - # Try to get GitHub username from the commit - GH_USER=$(gh api "/repos/${{ github.repository }}/commits/$(git rev-parse origin/main)" --jq '.author.login // empty' 2>/dev/null || echo "") + # These files are auto-generated during preview releases and must not + # be overwritten by main's versions (schema-check CI will reject changes + # to schemas/, and CHANGELOG.md tracks preview releases separately). + PREVIEW_HEAD=$(git rev-parse HEAD^1) + for f in schemas/agentcore.schema.v1.json CHANGELOG.md; do + if git show "$PREVIEW_HEAD:$f" > /dev/null 2>&1; then + git show "$PREVIEW_HEAD:$f" > "$f" + git add "$f" + echo " ↳ restored preview's $f" + fi + done + if ! git diff --cached --quiet; then + git commit -m "chore: restore preview-owned files (schema, changelog)" fi - echo "name=$AUTHOR" >> $GITHUB_OUTPUT - echo "gh_user=$GH_USER" >> $GITHUB_OUTPUT - env: - GH_TOKEN: ${{ github.token }} + + - name: Restore preview version and push + if: steps.merge.outputs.status == 'clean' + run: | + PREVIEW_VERSION="${{ steps.merge.outputs.preview_version }}" + CURRENT_VERSION=$(node -p "require('./package.json').version") + + if [[ "$CURRENT_VERSION" != "$PREVIEW_VERSION" ]]; then + PREVIEW_VERSION="$PREVIEW_VERSION" node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); + pkg.version = process.env.PREVIEW_VERSION; + fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n'); + " + if [[ -f package-lock.json ]]; then + PREVIEW_VERSION="$PREVIEW_VERSION" node -e " + const fs = require('fs'); + const lock = JSON.parse(fs.readFileSync('package-lock.json', 'utf8')); + lock.version = process.env.PREVIEW_VERSION; + if (lock.packages && lock.packages['']) { + lock.packages[''].version = process.env.PREVIEW_VERSION; + } + fs.writeFileSync('package-lock.json', JSON.stringify(lock, null, 2) + '\n'); + " + fi + git add package.json + [[ -f package-lock.json ]] && git add package-lock.json + git commit -m "chore: restore preview version ($PREVIEW_VERSION)" + fi + + git push origin HEAD:preview + echo "✅ main merged into preview and pushed" - name: Create PR for conflict resolution if: steps.merge.outputs.status == 'conflict' env: - GH_TOKEN: ${{ github.token }} - AUTHOR_NAME: ${{ steps.author.outputs.name }} - AUTHOR_GH: ${{ steps.author.outputs.gh_user }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | - BRANCH="sync-preview/merge-main-$(date +%Y%m%d-%H%M%S)" - - # Check if there's already an open sync PR - EXISTING=$(gh pr list --base preview --search "sync-preview: merge main into preview" --state open --json number --jq 'length') - if [[ "$EXISTING" != "0" ]]; then + # Check if there's already an open sync PR (match by branch prefix, not title search) + COUNT=$(gh pr list --base preview --state open --json headRefName \ + --jq '[.[] | select(.headRefName | startswith("sync-preview/"))] | length') + if [[ "$COUNT" != "0" ]]; then echo "ℹ️ Sync PR already open — skipping duplicate." exit 0 fi - # Create a branch from preview with the conflict markers + # Abort the failed merge and redo on a branch for the PR + git merge --abort + + BRANCH="sync-preview/merge-main-$(date +%Y%m%d-%H%M%S)" git checkout -b "$BRANCH" - git merge origin/main --no-edit -m "chore: merge main into preview" || true + git merge origin/main --no-edit -m "chore: merge main into preview (conflicts need resolution)" || true git add -A git commit --no-edit -m "chore: merge main into preview (conflicts need resolution)" || true git push origin "$BRANCH" - # Build mention string + GH_USER=$(gh api "/repos/${{ github.repository }}/commits/$(git rev-parse origin/main)" --jq '.author.login // empty' 2>/dev/null || echo "") MENTION="" - if [[ -n "$AUTHOR_GH" ]]; then - MENTION="cc @${AUTHOR_GH}" + if [[ -n "$GH_USER" ]]; then + MENTION="cc @${GH_USER}" fi gh pr create \ --base preview \ --head "$BRANCH" \ - --title "sync-preview: merge main into preview" \ + --title "sync-preview: merge main into preview (conflicts)" \ --body "$(cat <>>>>>> origin/main + +### Added +- feat: evo preview features — config bundles, batch evaluation, recommendations, AB testing (#1068) (9ccf802) +- feat: wire telemetry into all add.* commands (#1050) (e9dfc16) +- feat: make parsing resilient to individual failures (#1062) (a4c37a2) +- feat: update @aws/agent-inspector to 0.3.0 (90f17b4) +- feat: update @aws/agent-inspector to 0.3.0 (278783a) + +### Fixed +- fix: remove unnecessary non-null assertions after .default([]) revert (#1075) (eab8c87) +- fix: revert .optional() to .default([]) and strip empty evo arrays on write (#1074) (8c5cdfe) +- fix: remove dead preflight patch, proper teardown, optional evo schema fields (#1073) (839b32b) +- fix: remove dead preflight patch and use proper teardown for evo resources (#1072) (0e38e9e) +- fix: resolve e2e import test concurrency races (#1067) (bd6f841) +- fix: forward custom headers in bearer token invoke paths (#1065) (3dccd97) ### Other Changes +<<<<<<< HEAD - Merge pull request #1040 from aws/sync-preview/merge-main-20260429-v5 (dd76d17) - chore: merge main into preview (ecda10c) - fix(ci): install uv in release workflow prepare steps (#1038) (#1039) (01b3b7d) @@ -162,6 +199,10 @@ All notable changes to this project will be documented in this file. - chore: bump version to 0.11.0 (#967) (f8dc490) - test: add browser tests for agent inspector (#938) (7a4104d) - chore: bump version to 0.10.0 (#944) (12275c3) +======= +- refactor: consolidate cli-config into global-config (#802) (3aec000) +- ci: cut full e2e time in half via vitest sharding (#1016) (4daca83) +>>>>>>> origin/main ## [0.11.0] - 2026-04-24 diff --git a/integ-tests/add-remove-ab-test.test.ts b/integ-tests/add-remove-ab-test.test.ts index 551c86010..9a98ed017 100644 --- a/integ-tests/add-remove-ab-test.test.ts +++ b/integ-tests/add-remove-ab-test.test.ts @@ -5,8 +5,16 @@ import { readProjectConfig, runCLI, } from '../src/test-utils/index.js'; +<<<<<<< HEAD import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +======= +import { createTelemetryHelper } from '../src/test-utils/telemetry-helper.js'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const telemetry = createTelemetryHelper(); + +>>>>>>> origin/main async function runSuccess(args: string[], cwd: string) { const result = await runCLI(args, cwd); expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0); @@ -38,6 +46,10 @@ describe('integration: add and remove ab-test', () => { afterAll(async () => { await project.cleanup(); +<<<<<<< HEAD +======= + telemetry.destroy(); +>>>>>>> origin/main }); it('requires --name for JSON mode', async () => { @@ -154,6 +166,7 @@ describe('integration: add and remove ab-test', () => { }); it('removes ab-test', async () => { +<<<<<<< HEAD const json = await runSuccess(['remove', 'ab-test', '--name', 'MyIntegTest', '--json'], project.projectPath); expect(json.success).toBe(true); @@ -166,5 +179,28 @@ describe('integration: add and remove ab-test', () => { it('remove returns error for non-existent test', async () => { const json = await runFailure(['remove', 'ab-test', '--name', 'DoesNotExist', '--json'], project.projectPath); expect(json.error).toContain('not found'); +======= + const result = await runCLI(['remove', 'ab-test', '--name', 'MyIntegTest', '--json'], project.projectPath, { + env: telemetry.env, + }); + expect(result.exitCode).toBe(0); + const json = JSON.parse(result.stdout); + expect(json.success).toBe(true); + + const spec = await readProjectConfig(project.projectPath); + const abTest = spec.abTests?.find((t: { name: string }) => t.name === 'MyIntegTest'); + expect(abTest).toBeUndefined(); + telemetry.assertMetricEmitted({ command: 'remove.ab-test', exit_reason: 'success' }); + }); + + it('remove returns error for non-existent test', async () => { + const result = await runCLI(['remove', 'ab-test', '--name', 'DoesNotExist', '--json'], project.projectPath, { + env: telemetry.env, + }); + expect(result.exitCode).toBe(1); + const json = JSON.parse(result.stdout); + expect(json.error).toContain('not found'); + telemetry.assertMetricEmitted({ command: 'remove.ab-test', exit_reason: 'failure' }); +>>>>>>> origin/main }); }); diff --git a/integ-tests/add-remove-config-bundle.test.ts b/integ-tests/add-remove-config-bundle.test.ts index bd53e7f31..0df9bacb1 100644 --- a/integ-tests/add-remove-config-bundle.test.ts +++ b/integ-tests/add-remove-config-bundle.test.ts @@ -7,10 +7,19 @@ import { runFailure, runSuccess, } from '../src/test-utils/index.js'; +<<<<<<< HEAD +======= +import { createTelemetryHelper } from '../src/test-utils/telemetry-helper.js'; +>>>>>>> origin/main import { writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +<<<<<<< HEAD +======= +const telemetry = createTelemetryHelper(); + +>>>>>>> origin/main describe('integration: add and remove config-bundle', () => { let project: TestProject; @@ -20,6 +29,10 @@ describe('integration: add and remove config-bundle', () => { afterAll(async () => { await project.cleanup(); +<<<<<<< HEAD +======= + telemetry.destroy(); +>>>>>>> origin/main }); // ── Add lifecycle ───────────────────────────────────────────────────── @@ -40,7 +53,11 @@ describe('integration: add and remove config-bundle', () => { expect(json.bundleName).toBe('InlineBundle'); const config = await readProjectConfig(project.projectPath); +<<<<<<< HEAD const bundle = config.configBundles!.find(b => b.name === 'InlineBundle'); +======= + const bundle = config.configBundles.find(b => b.name === 'InlineBundle'); +>>>>>>> origin/main expect(bundle).toBeDefined(); expect(bundle!.type).toBe('ConfigurationBundle'); expect(bundle!.branchName).toBe('mainline'); @@ -68,7 +85,11 @@ describe('integration: add and remove config-bundle', () => { expect(json.bundleName).toBe('FileBundle'); const config = await readProjectConfig(project.projectPath); +<<<<<<< HEAD const bundle = config.configBundles!.find(b => b.name === 'FileBundle'); +======= + const bundle = config.configBundles.find(b => b.name === 'FileBundle'); +>>>>>>> origin/main expect(bundle).toBeDefined(); expect(Object.keys(bundle!.components)).toHaveLength(2); }); @@ -102,7 +123,11 @@ describe('integration: add and remove config-bundle', () => { expect(json.bundleName).toBe('FullOptsBundle'); const config = await readProjectConfig(project.projectPath); +<<<<<<< HEAD const bundle = config.configBundles!.find(b => b.name === 'FullOptsBundle'); +======= + const bundle = config.configBundles.find(b => b.name === 'FullOptsBundle'); +>>>>>>> origin/main expect(bundle).toBeDefined(); expect(bundle!.description).toBe('A bundle with all optional fields'); expect(bundle!.branchName).toBe('feature-branch'); @@ -127,7 +152,11 @@ describe('integration: add and remove config-bundle', () => { expect(json.bundleName).toBe('PlaceholderBundle'); const config = await readProjectConfig(project.projectPath); +<<<<<<< HEAD const bundle = config.configBundles!.find(b => b.name === 'PlaceholderBundle'); +======= + const bundle = config.configBundles.find(b => b.name === 'PlaceholderBundle'); +>>>>>>> origin/main expect(bundle).toBeDefined(); const keys = Object.keys(bundle!.components); expect(keys).toContain('{{runtime:AgentA}}'); @@ -228,6 +257,7 @@ describe('integration: add and remove config-bundle', () => { describe('remove config-bundle', () => { it('removes an existing config bundle', async () => { +<<<<<<< HEAD const json = await runSuccess( ['remove', 'config-bundle', '--name', 'InlineBundle', '--json'], project.projectPath @@ -247,18 +277,55 @@ describe('integration: add and remove config-bundle', () => { ); expect(json.error).toContain('not found'); +======= + const result = await runCLI( + ['remove', 'config-bundle', '--name', 'InlineBundle', '--json'], + project.projectPath, + { env: telemetry.env } + ); + + expect(result.exitCode).toBe(0); + const json = JSON.parse(result.stdout); + expect(json.success).toBe(true); + + const config = await readProjectConfig(project.projectPath); + const bundle = config.configBundles.find(b => b.name === 'InlineBundle'); + expect(bundle).toBeUndefined(); + telemetry.assertMetricEmitted({ command: 'remove.config-bundle', exit_reason: 'success' }); + }); + + it('returns error for non-existent bundle', async () => { + const result = await runCLI( + ['remove', 'config-bundle', '--name', 'DoesNotExist', '--json'], + project.projectPath, + { env: telemetry.env } + ); + + expect(result.exitCode).toBe(1); + const json = JSON.parse(result.stdout); + expect(json.error).toContain('not found'); + telemetry.assertMetricEmitted({ command: 'remove.config-bundle', exit_reason: 'failure' }); +>>>>>>> origin/main }); it('removes all remaining config bundles one by one', async () => { const configBefore = await readProjectConfig(project.projectPath); +<<<<<<< HEAD const remaining = configBefore.configBundles!.map(b => b.name); +======= + const remaining = configBefore.configBundles.map(b => b.name); +>>>>>>> origin/main for (const name of remaining) { await runSuccess(['remove', 'config-bundle', '--name', name, '--json'], project.projectPath); } const configAfter = await readProjectConfig(project.projectPath); +<<<<<<< HEAD expect(configAfter.configBundles!).toHaveLength(0); +======= + expect(configAfter.configBundles).toHaveLength(0); +>>>>>>> origin/main }); }); @@ -282,10 +349,17 @@ describe('integration: add and remove config-bundle', () => { } const config = await readProjectConfig(project.projectPath); +<<<<<<< HEAD expect(config.configBundles!).toHaveLength(bundleNames.length); for (const name of bundleNames) { expect(config.configBundles!.find(b => b.name === name)).toBeDefined(); +======= + expect(config.configBundles).toHaveLength(bundleNames.length); + + for (const name of bundleNames) { + expect(config.configBundles.find(b => b.name === name)).toBeDefined(); +>>>>>>> origin/main } }); @@ -293,10 +367,17 @@ describe('integration: add and remove config-bundle', () => { await runSuccess(['remove', 'config-bundle', '--name', 'BundleBeta', '--json'], project.projectPath); const config = await readProjectConfig(project.projectPath); +<<<<<<< HEAD expect(config.configBundles!).toHaveLength(2); expect(config.configBundles!.find(b => b.name === 'BundleAlpha')).toBeDefined(); expect(config.configBundles!.find(b => b.name === 'BundleGamma')).toBeDefined(); expect(config.configBundles!.find(b => b.name === 'BundleBeta')).toBeUndefined(); +======= + expect(config.configBundles).toHaveLength(2); + expect(config.configBundles.find(b => b.name === 'BundleAlpha')).toBeDefined(); + expect(config.configBundles.find(b => b.name === 'BundleGamma')).toBeDefined(); + expect(config.configBundles.find(b => b.name === 'BundleBeta')).toBeUndefined(); +>>>>>>> origin/main }); afterAll(async () => { diff --git a/integ-tests/add-remove-evaluator.test.ts b/integ-tests/add-remove-evaluator.test.ts index 30c3917c3..cee29f858 100644 --- a/integ-tests/add-remove-evaluator.test.ts +++ b/integ-tests/add-remove-evaluator.test.ts @@ -1,10 +1,13 @@ import { createTestProject, parseJsonOutput, readProjectConfig, runCLI } from '../src/test-utils/index.js'; import type { TestProject } from '../src/test-utils/index.js'; +import { createTelemetryHelper } from '../src/test-utils/telemetry-helper.js'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +const telemetry = createTelemetryHelper(); + /** Run a CLI command and assert it succeeds, returning parsed JSON output. */ async function runSuccess(args: string[], cwd: string) { - const result = await runCLI(args, cwd); + const result = await runCLI(args, cwd, { env: telemetry.env }); expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0); const json: unknown = parseJsonOutput(result.stdout); expect(json).toHaveProperty('success', true); @@ -13,7 +16,7 @@ async function runSuccess(args: string[], cwd: string) { /** Run a CLI command and assert it fails, returning parsed JSON output. */ async function runFailure(args: string[], cwd: string) { - const result = await runCLI(args, cwd); + const result = await runCLI(args, cwd, { env: telemetry.env }); expect(result.exitCode).toBe(1); const json: unknown = parseJsonOutput(result.stdout); expect(json).toHaveProperty('success', false); @@ -35,6 +38,7 @@ describe('integration: add and remove evaluators and online eval configs', () => afterAll(async () => { await project.cleanup(); + telemetry.destroy(); }); describe('evaluator and online eval lifecycle', () => { @@ -123,6 +127,7 @@ describe('integration: add and remove evaluators and online eval configs', () => const config = await readProjectConfig(project.projectPath); expect(config.onlineEvalConfigs.find(c => c.name === configName)).toBeUndefined(); + telemetry.assertMetricEmitted({ command: 'remove.online-eval', exit_reason: 'success' }); }); it('removes the evaluator after online eval is gone', async () => { @@ -130,6 +135,7 @@ describe('integration: add and remove evaluators and online eval configs', () => const config = await readProjectConfig(project.projectPath); expect(config.evaluators.find(e => e.name === evalName)).toBeUndefined(); + telemetry.assertMetricEmitted({ command: 'remove.evaluator', exit_reason: 'success' }); }); }); @@ -137,11 +143,13 @@ describe('integration: add and remove evaluators and online eval configs', () => it('fails to remove non-existent evaluator', async () => { const json = await runFailure(['remove', 'evaluator', '--name', 'NonExistent', '--json'], project.projectPath); expect(json.error).toContain('not found'); + telemetry.assertMetricEmitted({ command: 'remove.evaluator', exit_reason: 'failure' }); }); it('fails to remove non-existent online eval config', async () => { const json = await runFailure(['remove', 'online-eval', '--name', 'NonExistent', '--json'], project.projectPath); expect(json.error).toContain('not found'); + telemetry.assertMetricEmitted({ command: 'remove.online-eval', exit_reason: 'failure' }); }); it('rejects evaluator with missing --level', async () => { diff --git a/integ-tests/add-remove-resources.test.ts b/integ-tests/add-remove-resources.test.ts index a89c761dd..c668fe05d 100644 --- a/integ-tests/add-remove-resources.test.ts +++ b/integ-tests/add-remove-resources.test.ts @@ -41,7 +41,10 @@ describe('integration: add and remove resources', () => { const found = memories!.some((m: Record) => m.name === memoryName); expect(found, `Memory "${memoryName}" should be in config`).toBe(true); +<<<<<<< HEAD // Verify telemetry +======= +>>>>>>> origin/main telemetry.assertMetricEmitted({ command: 'add.memory', exit_reason: 'success' }); }); @@ -71,7 +74,10 @@ describe('integration: add and remove resources', () => { expect(episodic!.reflectionNamespaces, 'Should have reflectionNamespaces').toBeDefined(); expect(episodic!.reflectionNamespaces!.length).toBeGreaterThan(0); +<<<<<<< HEAD // Verify telemetry +======= +>>>>>>> origin/main telemetry.assertMetricEmitted({ command: 'add.memory', exit_reason: 'success', @@ -84,7 +90,9 @@ describe('integration: add and remove resources', () => { }); it('removes the memory resource', async () => { - const result = await runCLI(['remove', 'memory', '--name', memoryName, '--json'], project.projectPath); + const result = await runCLI(['remove', 'memory', '--name', memoryName, '--json'], project.projectPath, { + env: telemetry.env, + }); expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0); const json = JSON.parse(result.stdout); @@ -95,6 +103,8 @@ describe('integration: add and remove resources', () => { const memories = (config.memories as Record[] | undefined) ?? []; const found = memories.some((m: Record) => m.name === memoryName); expect(found, `Memory "${memoryName}" should be removed from config`).toBe(false); + + telemetry.assertMetricEmitted({ command: 'remove.memory', exit_reason: 'success' }); }); }); @@ -119,7 +129,10 @@ describe('integration: add and remove resources', () => { const found = credentials!.some((c: Record) => c.name === credentialName); expect(found, `Credential "${credentialName}" should be in config`).toBe(true); +<<<<<<< HEAD // Verify telemetry +======= +>>>>>>> origin/main telemetry.assertMetricEmitted({ command: 'add.credential', exit_reason: 'success', @@ -128,7 +141,9 @@ describe('integration: add and remove resources', () => { }); it('removes the credential resource', async () => { - const result = await runCLI(['remove', 'credential', '--name', credentialName, '--json'], project.projectPath); + const result = await runCLI(['remove', 'credential', '--name', credentialName, '--json'], project.projectPath, { + env: telemetry.env, + }); expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0); const json = JSON.parse(result.stdout); @@ -139,6 +154,71 @@ describe('integration: add and remove resources', () => { const credentials = (config.credentials as Record[] | undefined) ?? []; const found = credentials.some((c: Record) => c.name === credentialName); expect(found, `Credential "${credentialName}" should be removed from config`).toBe(false); + + telemetry.assertMetricEmitted({ command: 'remove.credential', exit_reason: 'success' }); + }); + }); + + describe('policy-engine', () => { + const engineName = `TestEngine${Date.now().toString().slice(-6)}`; + + it('adds a policy engine resource', async () => { + const result = await runCLI(['add', 'policy-engine', '--name', engineName, '--json'], project.projectPath, { + env: telemetry.env, + }); + + expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0); + const json = JSON.parse(result.stdout); + expect(json.success).toBe(true); + + telemetry.assertMetricEmitted({ + command: 'add.policy-engine', + exit_reason: 'success', + attach_gateway_count: '0', + }); + }); + + it('removes the policy engine resource', async () => { + const result = await runCLI(['remove', 'policy-engine', '--name', engineName, '--json'], project.projectPath, { + env: telemetry.env, + }); + + expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0); + + telemetry.assertMetricEmitted({ command: 'remove.policy-engine', exit_reason: 'success' }); + }); + }); + + describe('remove failure telemetry', () => { + it('emits failure telemetry for non-existent memory', async () => { + const result = await runCLI(['remove', 'memory', '--name', 'DoesNotExist', '--json'], project.projectPath, { + env: telemetry.env, + }); + + expect(result.exitCode).toBe(1); + telemetry.assertMetricEmitted({ command: 'remove.memory', exit_reason: 'failure' }); + }); + + it('emits failure telemetry for non-existent credential', async () => { + const result = await runCLI(['remove', 'credential', '--name', 'DoesNotExist', '--json'], project.projectPath, { + env: telemetry.env, + }); + + expect(result.exitCode).toBe(1); + telemetry.assertMetricEmitted({ command: 'remove.credential', exit_reason: 'failure' }); + }); + }); + + describe('remove all', () => { + it('resets all schemas and emits telemetry', async () => { + const result = await runCLI(['remove', 'all', '--yes', '--json'], project.projectPath, { + env: telemetry.env, + }); + + expect(result.exitCode).toBe(0); + const json = JSON.parse(result.stdout); + expect(json.success).toBe(true); + telemetry.assertMetricEmitted({ command: 'remove.all', exit_reason: 'success' }); }); }); diff --git a/integ-tests/create-edge-cases.test.ts b/integ-tests/create-edge-cases.test.ts index d1bbcf056..5b2d299fa 100644 --- a/integ-tests/create-edge-cases.test.ts +++ b/integ-tests/create-edge-cases.test.ts @@ -1,5 +1,6 @@ /* eslint-disable security/detect-non-literal-fs-filename */ import { exists, prereqs, runCLI } from '../src/test-utils/index.js'; +import { createTelemetryHelper } from '../src/test-utils/telemetry-helper.js'; import { randomUUID } from 'node:crypto'; import { mkdir, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; @@ -9,18 +10,21 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'; describe.skipIf(!prereqs.npm || !prereqs.git)('integration: create edge cases', () => { let testDir: string; + const telemetry = createTelemetryHelper(); + beforeAll(async () => { testDir = join(tmpdir(), `agentcore-integ-edge-${randomUUID()}`); await mkdir(testDir, { recursive: true }); }); afterAll(async () => { + telemetry.destroy(); await rm(testDir, { recursive: true, force: true }); }); describe('reserved names', () => { it('rejects reserved name "Test"', async () => { - const result = await runCLI(['create', '--name', 'Test', '--json'], testDir); + const result = await runCLI(['create', '--name', 'Test', '--json'], testDir, { env: telemetry.env }); expect(result.exitCode).toBe(1); const json = JSON.parse(result.stdout); @@ -30,6 +34,13 @@ describe.skipIf(!prereqs.npm || !prereqs.git)('integration: create edge cases', json.error.toLowerCase().includes('reserved') || json.error.toLowerCase().includes('conflict'), `Error should mention reserved/conflict: ${json.error}` ).toBeTruthy(); + + telemetry.assertMetricEmitted({ + command: 'create', + exit_reason: 'failure', + language: 'python', + has_agent: 'true', + }); }); it('rejects reserved name "bedrock"', async () => { @@ -121,12 +132,21 @@ describe.skipIf(!prereqs.npm || !prereqs.git)('integration: create edge cases', describe('flag interactions', () => { it('--defaults creates project with default settings', async () => { const name = `Def${Date.now().toString().slice(-6)}`; - const result = await runCLI(['create', '--name', name, '--defaults', '--json'], testDir); + const result = await runCLI(['create', '--name', name, '--defaults', '--json'], testDir, { env: telemetry.env }); expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); const json = JSON.parse(result.stdout); expect(json.success).toBe(true); expect(json.projectPath).toBeTruthy(); + + telemetry.assertMetricEmitted({ + command: 'create', + exit_reason: 'success', + language: 'python', + framework: 'strands', + model_provider: 'bedrock', + has_agent: 'true', + }); }); it('--dry-run shows what would be created without writing files', async () => { diff --git a/integ-tests/create-frameworks.test.ts b/integ-tests/create-frameworks.test.ts index 82bbc0871..1be05d237 100644 --- a/integ-tests/create-frameworks.test.ts +++ b/integ-tests/create-frameworks.test.ts @@ -1,4 +1,5 @@ import { exists, prereqs, readProjectConfig, runCLI } from '../src/test-utils/index.js'; +import { createTelemetryHelper } from '../src/test-utils/telemetry-helper.js'; import { randomUUID } from 'node:crypto'; import { mkdir, readFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; @@ -8,12 +9,15 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'; describe.skipIf(!prereqs.npm || !prereqs.git)('integration: create with different frameworks', () => { let testDir: string; + const telemetry = createTelemetryHelper(); + beforeAll(async () => { testDir = join(tmpdir(), `agentcore-integ-frameworks-${randomUUID()}`); await mkdir(testDir, { recursive: true }); }); afterAll(async () => { + telemetry.destroy(); await rm(testDir, { recursive: true, force: true }); }); @@ -34,7 +38,8 @@ describe.skipIf(!prereqs.npm || !prereqs.git)('integration: create with differen 'none', '--json', ], - testDir + testDir, + { env: telemetry.env } ); expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); @@ -60,6 +65,15 @@ describe.skipIf(!prereqs.npm || !prereqs.git)('integration: create with differen expect(agents).toBeDefined(); expect(agents.length).toBe(1); expect(agents[0]!.name).toBe(agentName); + + telemetry.assertMetricEmitted({ + command: 'create', + exit_reason: 'success', + language: 'python', + framework: 'langchain_langgraph', + model_provider: 'bedrock', + has_agent: 'true', + }); }); it('creates GoogleADK project with Gemini provider', async () => { diff --git a/package-lock.json b/package-lock.json index dbff7fe37..bba712d19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,20 @@ { "name": "@aws/agentcore", +<<<<<<< HEAD "version": "1.0.0-preview.8", +======= + "version": "0.13.1", +>>>>>>> origin/main "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@aws/agentcore", +<<<<<<< HEAD "version": "1.0.0-preview.8", +======= + "version": "0.13.1", +>>>>>>> origin/main "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -15,7 +23,7 @@ "@aws-sdk/client-bedrock": "^3.1012.0", "@aws-sdk/client-bedrock-agent": "^3.1012.0", "@aws-sdk/client-bedrock-agentcore": "^3.1020.0", - "@aws-sdk/client-bedrock-agentcore-control": "^3.1020.0", + "@aws-sdk/client-bedrock-agentcore-control": "^3.1039.0", "@aws-sdk/client-bedrock-runtime": "^3.893.0", "@aws-sdk/client-cloudformation": "^3.893.0", "@aws-sdk/client-cloudwatch-logs": "^3.893.0", @@ -27,7 +35,11 @@ "@aws/agent-inspector": "0.3.0", "@commander-js/extra-typings": "^14.0.0", "@opentelemetry/api": "^1.9.1", +<<<<<<< HEAD "@opentelemetry/exporter-metrics-otlp-http": "^0.217.0", +======= + "@opentelemetry/exporter-metrics-otlp-http": "^0.215.0", +>>>>>>> origin/main "@opentelemetry/otlp-transformer": "^0.213.0", "@opentelemetry/resources": "^2.6.1", "@opentelemetry/sdk-metrics": "^2.6.1", @@ -242,9 +254,9 @@ } }, "node_modules/@aws-cdk/cloud-assembly-schema": { - "version": "53.19.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-53.19.0.tgz", - "integrity": "sha512-pHtEeuiPwYu4vWJhqTqBCvOWy2w4BjYFW5W8L5lH5AzhKLLG92o+Ck0oLcKUkB0Ucl4ff49WL9K7sa7g35ionw==", + "version": "53.18.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-53.18.0.tgz", + "integrity": "sha512-/fa6rOpokkfa5tVIdhsaexQq5MVVTSsZSD1Tu45YcrdyGRusGrM9RlPMCPrwvMS1UfdVFBhcgO9dl9ODWAWOeQ==", "bundleDependencies": [ "jsonschema", "semver" @@ -377,14 +389,14 @@ } }, "node_modules/@aws-cdk/toolkit-lib": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/toolkit-lib/-/toolkit-lib-1.25.0.tgz", - "integrity": "sha512-yTxQLcI7T8wfUAhWw8vO7dG//x0WL786AA5u4i/yRtSVSJd+0frHX6gD+njSv41OaNLhwP6EVYkFz9wTyCt64A==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/toolkit-lib/-/toolkit-lib-1.24.0.tgz", + "integrity": "sha512-tgtH0CJ8/N/CpT1/ebOBfUpxdAMSRsP9LTAjWfa+E0clX4Vuvx0w1J1bGYwtvKY9nQUbFIO4QfgNEHz8hVlMUA==", "license": "Apache-2.0", "dependencies": { "@aws-cdk/cdk-assets-lib": "^1", "@aws-cdk/cloud-assembly-api": "2.2.2", - "@aws-cdk/cloud-assembly-schema": ">=53.19.0", + "@aws-cdk/cloud-assembly-schema": ">=53.18.0", "@aws-cdk/cloudformation-diff": "^2", "@aws-cdk/cx-api": "^2", "@aws-sdk/client-appsync": "^3", @@ -415,7 +427,7 @@ "@smithy/util-retry": "^4", "@smithy/util-waiter": "^4", "archiver": "^7.0.1", - "cdk-from-cfn": "^0.297.0", + "cdk-from-cfn": "^0.295.0", "chalk": "^4", "chokidar": "^4", "fast-deep-equal": "^3.1.3", @@ -687,24 +699,24 @@ } }, "node_modules/@aws-sdk/client-application-signals": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-application-signals/-/client-application-signals-3.1038.0.tgz", - "integrity": "sha512-kUBaXESTxZO+JiMQIFU/Wv0Lop+KtuJP0xOv00dL/4a0RdcqNdAwbH7RnxXaL5z7toDLCfg9j93f0IQEdBLbXA==", + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-application-signals/-/client-application-signals-3.1037.0.tgz", + "integrity": "sha512-xnGVyIWU1SXNSnnARvU3U3sic0QWH0wek/X3WpXFCpOm0NBzbTablWiAszNDU9RCvg9KUDm6Wdp0T4jnodXhEg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-node": "^3.972.37", + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/credential-provider-node": "^3.972.36", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.35", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.21", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", @@ -712,7 +724,7 @@ "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.5", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -728,7 +740,7 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.4", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -788,25 +800,25 @@ } }, "node_modules/@aws-sdk/client-bedrock": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock/-/client-bedrock-3.1038.0.tgz", - "integrity": "sha512-WY99Vodg7V4hxLQn7HOLawXHeVYv8Ys16Xx3CPpu8L7+1spvO/i4uykzTXH6GkojdAqNO2CSclhk31lb85nSWg==", + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock/-/client-bedrock-3.1037.0.tgz", + "integrity": "sha512-XGuJ86vuuEsqp0Gq8fMCSMd/VNCwqTvKwFT99SU2OOLyNp31ChZ+LdIckJZl/A3jpUyZYpXjn7IxP/N/6UFiZA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-node": "^3.972.37", + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/credential-provider-node": "^3.972.36", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.35", "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/token-providers": "3.1038.0", + "@aws-sdk/token-providers": "3.1037.0", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.21", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", @@ -814,7 +826,7 @@ "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.5", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -830,7 +842,7 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.4", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -839,24 +851,24 @@ } }, "node_modules/@aws-sdk/client-bedrock-agent": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-agent/-/client-bedrock-agent-3.1038.0.tgz", - "integrity": "sha512-cwOkwVhYs+GeW7SzNS/pmgkHZ/SAyv4wIkz62vew6sM6R+XgqqBbtEqDrf+FSsprFtx75edbduRg8boJt0veSg==", + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-agent/-/client-bedrock-agent-3.1037.0.tgz", + "integrity": "sha512-8Uc3zdwfxmjMrXb4qP69bByL054R+jWNaW3/Hq93E1jnag8vZz8YhJlz1x6jr1oXOoOLVErc1L/g8x0sMFZuRA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-node": "^3.972.37", + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/credential-provider-node": "^3.972.36", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.35", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.21", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", @@ -864,7 +876,7 @@ "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.5", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -880,7 +892,7 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.4", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -889,24 +901,24 @@ } }, "node_modules/@aws-sdk/client-bedrock-agentcore": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-agentcore/-/client-bedrock-agentcore-3.1038.0.tgz", - "integrity": "sha512-HHsWj3AWsBnvihV0oUolOlnPxXum75kTdQHuYMX6kko4ek2iQWOe1WxUQG4TwOS7HUivU3Zf6CNti/K6wEkc7A==", + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-agentcore/-/client-bedrock-agentcore-3.1037.0.tgz", + "integrity": "sha512-8WmZulMmFnCWFuX2rDBoZdebCMmmrAi1VABsLgm4O73w3+s7tcON1YgspG9gTevuVRtOVdk1B6TLw2Mo8NBHSQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-node": "^3.972.37", + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/credential-provider-node": "^3.972.36", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.35", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.21", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/eventstream-serde-browser": "^4.2.14", @@ -917,7 +929,7 @@ "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.5", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -933,7 +945,7 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.4", "@smithy/util-stream": "^4.5.25", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" @@ -943,24 +955,24 @@ } }, "node_modules/@aws-sdk/client-bedrock-agentcore-control": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-agentcore-control/-/client-bedrock-agentcore-control-3.1038.0.tgz", - "integrity": "sha512-S+BtFwV7C5yUwjMZwExp0hAmdMCXLA2iG6KPvoS4zv8T2bMk1itrWfE4opqSZ1lgwDmnazcG7mmfAAUBFXvKXw==", + "version": "3.1039.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-agentcore-control/-/client-bedrock-agentcore-control-3.1039.0.tgz", + "integrity": "sha512-YoJ1rYHikWbsBuTyjADxtYzD+PXmyR2X6NVWkX4AqUtZT1YJC1HW+BsFABlXfItsEOjss3kpsjQHnlMbiCBtig==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-node": "^3.972.37", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/credential-provider-node": "^3.972.38", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.37", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.23", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", @@ -968,7 +980,7 @@ "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.7", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -984,7 +996,7 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.6", "@smithy/util-utf8": "^4.2.2", "@smithy/util-waiter": "^4.3.0", "tslib": "^2.6.2" @@ -994,28 +1006,28 @@ } }, "node_modules/@aws-sdk/client-bedrock-runtime": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1038.0.tgz", - "integrity": "sha512-oGiqs9v9WzPOdv7PDdm9iPibHgrbDvCDyNg43wFZn2PiiEUisFM+xUP2CRMsj41SmwZPhohmZkXiUu1+MghbAQ==", + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1037.0.tgz", + "integrity": "sha512-Evla4DUdBf1pQpQa7pbfquj7jRaRktkI0qGoWBJBXWB9wQISzJ8OEI4sHugk/W6SF47C7hMP/o3Z/XBrfnejCw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-node": "^3.972.37", + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/credential-provider-node": "^3.972.36", "@aws-sdk/eventstream-handler-node": "^3.972.14", "@aws-sdk/middleware-eventstream": "^3.972.10", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.35", "@aws-sdk/middleware-websocket": "^3.972.16", "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/token-providers": "3.1038.0", + "@aws-sdk/token-providers": "3.1037.0", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.21", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/eventstream-serde-browser": "^4.2.14", @@ -1026,7 +1038,7 @@ "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.5", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -1042,7 +1054,7 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.4", "@smithy/util-stream": "^4.5.25", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" @@ -1051,6 +1063,42 @@ "node": ">=20.0.0" } }, + "node_modules/@aws-sdk/client-bedrock-runtime/node_modules/@aws-sdk/token-providers": { + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1037.0.tgz", + "integrity": "sha512-csxa484KboWLs3f8jFQ5v9RwH8FVf0fQ+SO3GSXyu4Jtinhh4qXmOWLSVX30RBpB933dZaKGHGEXzEEY88NqRw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/nested-clients": "^3.997.3", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/client-bedrock/node_modules/@aws-sdk/token-providers": { + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1037.0.tgz", + "integrity": "sha512-csxa484KboWLs3f8jFQ5v9RwH8FVf0fQ+SO3GSXyu4Jtinhh4qXmOWLSVX30RBpB933dZaKGHGEXzEEY88NqRw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/nested-clients": "^3.997.3", + "@aws-sdk/types": "^3.973.8", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@aws-sdk/client-cloudcontrol": { "version": "3.1036.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudcontrol/-/client-cloudcontrol-3.1036.0.tgz", @@ -1103,24 +1151,24 @@ } }, "node_modules/@aws-sdk/client-cloudformation": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.1038.0.tgz", - "integrity": "sha512-5c8u4magoWj8uAhiKoBY0/FXqCRqw3g05RXiQzUdNbeZDhfnv2b/r0aITVLlVEW/zfQUGi/WJfaV4vuffp6k1A==", + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.1037.0.tgz", + "integrity": "sha512-nLSwtmayv7tjjp6t8Lc20xZCeA+XJ5UzXvauQCnO3aRZVAxrgarQntZjS+eWlRYGRqLBjXSre4xL7XwUlObb2A==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-node": "^3.972.37", + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/credential-provider-node": "^3.972.36", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.35", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.21", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", @@ -1128,7 +1176,7 @@ "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.5", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -1144,9 +1192,9 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.4", "@smithy/util-utf8": "^4.2.2", - "@smithy/util-waiter": "^4.3.0", + "@smithy/util-waiter": "^4.2.16", "tslib": "^2.6.2" }, "engines": { @@ -1154,24 +1202,24 @@ } }, "node_modules/@aws-sdk/client-cloudwatch-logs": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.1038.0.tgz", - "integrity": "sha512-OfdF+xZiiHWR5JL9rsc8jjlk1fNxnX2uS2iWWBM9vncVSXdhPAMb/rFNeHLkIx9IfmyiNi7kXUTAKgIUkVN0+w==", + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.1037.0.tgz", + "integrity": "sha512-W0TRDfyBikNR+DzOTBgBLT4TqVHCAasqx2Xu4G4PfTRCansUtEJRydq0CEVOpHlMfme4Va89O/r9sp/VoDsKRg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-node": "^3.972.37", + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/credential-provider-node": "^3.972.36", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.35", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.21", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/eventstream-serde-browser": "^4.2.14", @@ -1182,7 +1230,7 @@ "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.5", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -1198,7 +1246,7 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.4", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -1257,24 +1305,24 @@ } }, "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.1038.0.tgz", - "integrity": "sha512-tTSXUZXzydM0VUoxcrM4YrhhQfFgepfpbRLEq460650rFAC8NsGhGQ6Ixo7UPV6TKEyI/jQcCnQVi4RVM4SkAg==", + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.1037.0.tgz", + "integrity": "sha512-/BQAyz98JRQFg3E8de3fGGydIYnsFRd6Cla4+zkviOe641fLCG0ZkPIk9D22HSi8qy9XKx+zk6ed2PcLO8uuPw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-node": "^3.972.37", + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/credential-provider-node": "^3.972.36", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.35", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.21", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", @@ -1282,7 +1330,7 @@ "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.5", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -1298,7 +1346,7 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.4", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -1307,25 +1355,25 @@ } }, "node_modules/@aws-sdk/client-cognito-identity-provider": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity-provider/-/client-cognito-identity-provider-3.1038.0.tgz", - "integrity": "sha512-E3/2sei5wiUvS+ZdlvQ93SXU2C9zdqq1mJqxik6B8GHtSnP2T4J97Hdu46FM6GK/HZJ+iFZFeIlM0B+P3uH2QQ==", + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity-provider/-/client-cognito-identity-provider-3.1037.0.tgz", + "integrity": "sha512-w0HuaMNtzcj6bErBX8/TVGbOz0a8JNCzPHLMq2u/ll4uuxl9Xut0njuy7vyY0/pYCuNE+no4uq+yHwn8U3ptgw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-node": "^3.972.37", + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/credential-provider-node": "^3.972.36", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.35", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.21", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", @@ -1333,7 +1381,7 @@ "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.5", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -1349,7 +1397,7 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.4", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -1719,24 +1767,24 @@ } }, "node_modules/@aws-sdk/client-resource-groups-tagging-api": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-resource-groups-tagging-api/-/client-resource-groups-tagging-api-3.1038.0.tgz", - "integrity": "sha512-jEjVlhcAb4j658XKfxBPyyrbXJKmfg2bR3Xokqy5lhvZA80t6p47jeQ4s5yXIsaNzxnefJ8SuqrVft2KBHbDrQ==", + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-resource-groups-tagging-api/-/client-resource-groups-tagging-api-3.1037.0.tgz", + "integrity": "sha512-1+sv7vbSSRqVMBTvgFPFopXZ/SGdz3jP9aIHM8eOIuuZGYSmuqiXkNd7Ag5yxIQqEDO8sASevCqscEqN0f4OUw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-node": "^3.972.37", + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/credential-provider-node": "^3.972.36", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.35", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.21", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", @@ -1744,7 +1792,7 @@ "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.5", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -1760,7 +1808,7 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.4", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -1821,32 +1869,32 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1038.0.tgz", - "integrity": "sha512-k60qm50bWkaqNfCJe1z28WaqgpztE0wbWVMZw6ZJcTOGfrWFhsJeLCEqtkH8w00iEozKx9GQwdQXz4G0sMGdKA==", + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1037.0.tgz", + "integrity": "sha512-DBmA1jAW8ST6C4srBxeL1/RLIir/d8WOm4s4mi59mGp6mBktHM59Kwb7GuURaCO60cotuce5zr0sKpMLPcBQyA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-node": "^3.972.37", + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/credential-provider-node": "^3.972.36", "@aws-sdk/middleware-bucket-endpoint": "^3.972.10", "@aws-sdk/middleware-expect-continue": "^3.972.10", - "@aws-sdk/middleware-flexible-checksums": "^3.974.14", + "@aws-sdk/middleware-flexible-checksums": "^3.974.13", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-location-constraint": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-sdk-s3": "^3.972.35", + "@aws-sdk/middleware-sdk-s3": "^3.972.34", "@aws-sdk/middleware-ssec": "^3.972.10", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.35", "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/signature-v4-multi-region": "^3.996.23", + "@aws-sdk/signature-v4-multi-region": "^3.996.22", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.21", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/eventstream-serde-browser": "^4.2.14", @@ -1860,7 +1908,7 @@ "@smithy/md5-js": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.5", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -1876,10 +1924,10 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.4", "@smithy/util-stream": "^4.5.25", "@smithy/util-utf8": "^4.2.2", - "@smithy/util-waiter": "^4.3.0", + "@smithy/util-waiter": "^4.2.16", "tslib": "^2.6.2" }, "engines": { @@ -2038,25 +2086,25 @@ } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.1038.0.tgz", - "integrity": "sha512-lboBAXDIr+ot5a357mQgaAwgMMYZW7EwO216LTASUHV3UN4YgqskrEcwsDV9765KH9wUDGxFt8rClS4ixaOgiA==", + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.1037.0.tgz", + "integrity": "sha512-Ye+BEvy1Fd/JtqfF1T9PiodIU52/Cd9sP4oBLnj8QQEyYRUcYG1OQ2xIFXF/gzAAMjfVN8HqGJo9LxdmScxZAQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-node": "^3.972.37", + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/credential-provider-node": "^3.972.36", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.35", "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/signature-v4-multi-region": "^3.996.23", + "@aws-sdk/signature-v4-multi-region": "^3.996.22", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.21", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", @@ -2064,7 +2112,7 @@ "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.5", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -2080,7 +2128,7 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.4", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -2089,24 +2137,24 @@ } }, "node_modules/@aws-sdk/client-xray": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-xray/-/client-xray-3.1038.0.tgz", - "integrity": "sha512-wPO92YHLQDbaHog82RFCmrGmMl5bdF6SOcejBr/RGcORsL4t6RXvaVP9sF73txgOTDRgUQh/EO1uy/JmTC9Dgg==", + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-xray/-/client-xray-3.1037.0.tgz", + "integrity": "sha512-cnic610qpFrbR3gNw0pFi6EFrkDhDJOHlOnQzpdjBQ38O3QXwkON6Kco8IyTs7TlyOr7HRmHnBiEYVDZrLVC0w==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-node": "^3.972.37", + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/credential-provider-node": "^3.972.36", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.35", "@aws-sdk/region-config-resolver": "^3.972.13", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.21", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", @@ -2114,7 +2162,7 @@ "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.5", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -2130,7 +2178,7 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.4", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -2139,9 +2187,15 @@ } }, "node_modules/@aws-sdk/core": { +<<<<<<< HEAD "version": "3.974.8", "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.8.tgz", "integrity": "sha512-njR2qoG6ZuB0kvAS2FyICsFZJ6gmCcf2X/7JcD14sUvGDm26wiZ5BrA6LOiUxKFEF+IVe7kdroxyE00YlkiYsw==", +======= + "version": "3.974.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.7.tgz", + "integrity": "sha512-YhRC90ofz5oolTJZlA8voU/oUrCB2azi8Usx51k8hhB5LpWbYQMMXKUqSqkoL0Cru+RQJgWTHpAfEDDIwfUhJw==", +>>>>>>> origin/main "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.8", @@ -2211,12 +2265,12 @@ } }, "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.972.29", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.29.tgz", - "integrity": "sha512-fklwtMw+9+1TRNa7KOCaaE9P9ubN6PdKCVlviX/vPRNtnMGIivAFrWcYsAcyw+sHPPioiSCSOHKKAhtOkO6IGg==", + "version": "3.972.28", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.28.tgz", + "integrity": "sha512-UXhc4FfxbfNaIqycDnIZ+W8CMAoCtcJJfZkq+cWSUwQRN0V0d0uAoN2qCFyKZip8inlHeKJmNQsPliKKcElP8Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/nested-clients": "^3.997.4", + "@aws-sdk/nested-clients": "^3.997.3", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", @@ -2227,12 +2281,12 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.32", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.32.tgz", - "integrity": "sha512-7vA4GHg8NSmQxquJHSBcSM3RgB4ZaaRi6u4+zGFKOmOH6aqlgr2Sda46clkZDYzlirgfY96w15Zj0jh6PT48ng==", + "version": "3.972.33", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.33.tgz", + "integrity": "sha512-bJV7eViSJV6GSuuN+VIdNVPdwPsNSf75BiC2v5alPrjR/OCcqgKwSZInKbDFz9mNeizldsyf67jt6YSIiv53Cw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", + "@aws-sdk/core": "^3.974.7", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", @@ -2243,12 +2297,12 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.34", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.34.tgz", - "integrity": "sha512-vBrhWujFCLp1u8ptJRWYlipMutzPptb8pDQ00rKVH9q67T7rGd3VTWIj63aKrlLuY6qSsw1Rt5F/D/7wnNgryA==", + "version": "3.972.35", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.35.tgz", + "integrity": "sha512-x/BQGEIdq0oI+4WxLjKmnQvT7CnF9r8ezdGt7wXwxb7ckHXQz0Zmgxt8v3Ne0JaT3R5YefmuybHX6E8EnsDXyA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", + "@aws-sdk/core": "^3.974.7", "@aws-sdk/types": "^3.973.8", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.6.1", @@ -2264,19 +2318,19 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.36", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.36.tgz", - "integrity": "sha512-FBHyCmV8EB0gUvh1d+CZm87zt2PrdC7OyWexLRoH3I5zWSOUGa+9t58Y5jbxRfwUp3AWpHAFvKY6YzgR845sVA==", + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.37.tgz", + "integrity": "sha512-eUTpmWfd/BKsq9medhCRcu+GRAhFP2Zrn7/2jKDHHOOjCkhrMoTp/t4cEthqFoG7gE0VGp5wUxrXTdvBCmSmJg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-env": "^3.972.32", - "@aws-sdk/credential-provider-http": "^3.972.34", - "@aws-sdk/credential-provider-login": "^3.972.36", - "@aws-sdk/credential-provider-process": "^3.972.32", - "@aws-sdk/credential-provider-sso": "^3.972.36", - "@aws-sdk/credential-provider-web-identity": "^3.972.36", - "@aws-sdk/nested-clients": "^3.997.4", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/credential-provider-env": "^3.972.33", + "@aws-sdk/credential-provider-http": "^3.972.35", + "@aws-sdk/credential-provider-login": "^3.972.37", + "@aws-sdk/credential-provider-process": "^3.972.33", + "@aws-sdk/credential-provider-sso": "^3.972.37", + "@aws-sdk/credential-provider-web-identity": "^3.972.37", + "@aws-sdk/nested-clients": "^3.997.5", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", @@ -2289,13 +2343,13 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.36", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.36.tgz", - "integrity": "sha512-IFap01lJKxQc0C/OHmZwZQr/cKq0DhrcmKedRrdnnl42D+P0SImnnnWQjv07uIPqpEdtqmkPXb9TiPYTU+prxQ==", + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.37.tgz", + "integrity": "sha512-Ty68y8ISSC+g5Q3D0K8uAaoINwvfaOslnNpsF/LgVUxyosYXHawcK2yV4HLXDVugiTTYLQfJfcw0ce5meAGkKw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/nested-clients": "^3.997.4", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/nested-clients": "^3.997.5", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", @@ -2308,17 +2362,17 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.37", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.37.tgz", - "integrity": "sha512-/WFixFAAiw8WpmjZcI0l4t3DerXLmVinOIfuotmRZnu2qmsFPoqqmstASz0z8bi1pGdFXzeLzf6bwucM3mZcUQ==", + "version": "3.972.38", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.38.tgz", + "integrity": "sha512-BQ9XYnBDVxR2HuV5huXYQYF/PZMTsY+EnwfGnCU2cA8Zw63XpkOtPY8WqiMIZMQCrKPQQEiFURS/o9CIolRLqg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.32", - "@aws-sdk/credential-provider-http": "^3.972.34", - "@aws-sdk/credential-provider-ini": "^3.972.36", - "@aws-sdk/credential-provider-process": "^3.972.32", - "@aws-sdk/credential-provider-sso": "^3.972.36", - "@aws-sdk/credential-provider-web-identity": "^3.972.36", + "@aws-sdk/credential-provider-env": "^3.972.33", + "@aws-sdk/credential-provider-http": "^3.972.35", + "@aws-sdk/credential-provider-ini": "^3.972.37", + "@aws-sdk/credential-provider-process": "^3.972.33", + "@aws-sdk/credential-provider-sso": "^3.972.37", + "@aws-sdk/credential-provider-web-identity": "^3.972.37", "@aws-sdk/types": "^3.973.8", "@smithy/credential-provider-imds": "^4.2.14", "@smithy/property-provider": "^4.2.14", @@ -2331,12 +2385,12 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.32", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.32.tgz", - "integrity": "sha512-uZp4tlGbpczV8QxmtIwOpSkcyGtBRR8/T4BAumRKfAt1nwCig3FSCZvrKl6ARDIDVRYn5p2oRcAsfFR01EgMGA==", + "version": "3.972.33", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.33.tgz", + "integrity": "sha512-yfjGksI9WQbdMObb0VeLXqzTLI+a0qXLJT9gCDiv0+X/xjPpI3mTz6a5FibrhpuEKIe0gSgvs3MaoFZy5cx4WA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", + "@aws-sdk/core": "^3.974.7", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", @@ -2348,14 +2402,14 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.36", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.36.tgz", - "integrity": "sha512-DsLr0UHMyKzRJKe2bjlwU8q1cfoXg8TIJKV/xwvnalAemiZLOZunFzj/whGnFDZIBVLdnbLiwv5SvRf1+CSwkg==", + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.37.tgz", + "integrity": "sha512-fpwE+20ntpp3i9Xb9vUuQfXLDKYHH+5I2V+ZG96SX1nBzrruhy10RXDgmN7t1etOz3c55stlA3TeQASUA451NQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/nested-clients": "^3.997.4", - "@aws-sdk/token-providers": "3.1038.0", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/nested-clients": "^3.997.5", + "@aws-sdk/token-providers": "3.1039.0", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", @@ -2367,13 +2421,13 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.36", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.36.tgz", - "integrity": "sha512-uzrURO7frJhHQVVNR5zBJcCYeMYflmXcWBK1+MiBym2Dfjh6nXATrMixrmGZi+97Q7ETZ+y/4lUwAy0Nfnznjw==", + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.37.tgz", + "integrity": "sha512-aryawqyebf+3WhAFNHfF62rekFpYtVcVN7dQ89qnAWsa4n5hJst8qBG6gXC24WHtW7Nnhkf9ScYnjwo0Brn3bw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/nested-clients": "^3.997.4", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/nested-clients": "^3.997.5", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", @@ -2385,23 +2439,23 @@ } }, "node_modules/@aws-sdk/credential-providers": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.1038.0.tgz", - "integrity": "sha512-+B9BuRVPPKF0Q6msVS4vUGOsL4eUg7XYogikp56rUEQVoUVxn5ONyWlnNzsDMTv+BwuBgFo5N7gRZtEToAnSgg==", + "version": "3.1037.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.1037.0.tgz", + "integrity": "sha512-TPPoQzfNkWltNgjJn3RRY1S8VXffDvv49xGGs9K0DrYS9LZCLLsoHmSmShx9HQusPc/4Oz23rfRWTolCU19PdQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-cognito-identity": "3.1038.0", - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/credential-provider-cognito-identity": "^3.972.29", - "@aws-sdk/credential-provider-env": "^3.972.32", - "@aws-sdk/credential-provider-http": "^3.972.34", - "@aws-sdk/credential-provider-ini": "^3.972.36", - "@aws-sdk/credential-provider-login": "^3.972.36", - "@aws-sdk/credential-provider-node": "^3.972.37", - "@aws-sdk/credential-provider-process": "^3.972.32", - "@aws-sdk/credential-provider-sso": "^3.972.36", - "@aws-sdk/credential-provider-web-identity": "^3.972.36", - "@aws-sdk/nested-clients": "^3.997.4", + "@aws-sdk/client-cognito-identity": "3.1037.0", + "@aws-sdk/core": "^3.974.5", + "@aws-sdk/credential-provider-cognito-identity": "^3.972.28", + "@aws-sdk/credential-provider-env": "^3.972.31", + "@aws-sdk/credential-provider-http": "^3.972.33", + "@aws-sdk/credential-provider-ini": "^3.972.35", + "@aws-sdk/credential-provider-login": "^3.972.35", + "@aws-sdk/credential-provider-node": "^3.972.36", + "@aws-sdk/credential-provider-process": "^3.972.31", + "@aws-sdk/credential-provider-sso": "^3.972.35", + "@aws-sdk/credential-provider-web-identity": "^3.972.35", + "@aws-sdk/nested-clients": "^3.997.3", "@aws-sdk/types": "^3.973.8", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", @@ -2519,15 +2573,15 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.974.14", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.14.tgz", - "integrity": "sha512-mhTO3amGzYv/DQNbbqZo6UkHquBHlEEVRZwXmjeRqLmy1l9z3xCiFzglPL7n9JpVc2DZc9kjaraAn3JQrueZbw==", + "version": "3.974.13", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.13.tgz", + "integrity": "sha512-b6QUe2hQX9XsnCzp6mtzVaERhganDKeb8lmGL6pVhr7rRVH9S9keDFW7uKytuuqmcY5943FixoGqn/QL+sbUBA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "^3.974.6", + "@aws-sdk/core": "^3.974.5", "@aws-sdk/crc64-nvme": "^3.972.7", "@aws-sdk/types": "^3.973.8", "@smithy/is-array-buffer": "^4.2.2", @@ -2636,12 +2690,12 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.35", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.35.tgz", - "integrity": "sha512-lLppaNTAz+wNgLdi4FtHzrlwrGF0ODTnBWHBaFg85SKs0eJ+M+tP5ifrA8f/0lNd+Ak3MC1NGC6RavV3ny4HTg==", + "version": "3.972.36", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.36.tgz", + "integrity": "sha512-YhPix+0x/MdQrb1Ug1GDKeS5fqylIy+naz800asX8II4jqfTk2KY2KhmmYCwZcky8YWtRQQwWCGdoqeAnip8Uw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", + "@aws-sdk/core": "^3.974.7", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/core": "^3.23.17", @@ -2675,18 +2729,18 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.36", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.36.tgz", - "integrity": "sha512-O2beToxguBvrZFFZ+fFgPbbae8MvyIBjQ6lImee4APHEXXNAD5ZJ2ayLF1mb7rsKw86TM81y5czg82bZncjSjg==", + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.37.tgz", + "integrity": "sha512-N1oNpdiLoVAWYD3WFBnUi3LlfoDA06ZHo4ozyjbsJNLvILzvt//0CnR8N+CZ0NWeYgVB/5V59ivixHCWCx2ALw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", + "@aws-sdk/core": "^3.974.7", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@smithy/core": "^3.23.17", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.6", "tslib": "^2.6.2" }, "engines": { @@ -2717,24 +2771,24 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.4", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.4.tgz", - "integrity": "sha512-4Sf+WY1lMJzXlw5MiyCMe/UzdILCwvuaHThbqMXS6dfh9gZy3No360I42RXquOI/ULUOhWy2HCyU0Fp20fQGPQ==", + "version": "3.997.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.5.tgz", + "integrity": "sha512-jGFr6DxtcMTmzOkG/a0jCZYv4BBDmeNYVeO+/memSoDkYCJu4Y58xviYmzwJfYyIVSts+X/BVjJm1uGBnwHEMg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.6", + "@aws-sdk/core": "^3.974.7", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.37", "@aws-sdk/region-config-resolver": "^3.972.13", - "@aws-sdk/signature-v4-multi-region": "^3.996.23", + "@aws-sdk/signature-v4-multi-region": "^3.996.24", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.8", "@aws-sdk/util-user-agent-browser": "^3.972.10", - "@aws-sdk/util-user-agent-node": "^3.973.22", + "@aws-sdk/util-user-agent-node": "^3.973.23", "@smithy/config-resolver": "^4.4.17", "@smithy/core": "^3.23.17", "@smithy/fetch-http-handler": "^5.3.17", @@ -2742,7 +2796,7 @@ "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.32", - "@smithy/middleware-retry": "^4.5.6", + "@smithy/middleware-retry": "^4.5.7", "@smithy/middleware-serde": "^4.2.20", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", @@ -2758,7 +2812,7 @@ "@smithy/util-defaults-mode-node": "^4.2.54", "@smithy/util-endpoints": "^3.4.2", "@smithy/util-middleware": "^4.2.14", - "@smithy/util-retry": "^4.3.5", + "@smithy/util-retry": "^4.3.6", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -2783,12 +2837,12 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.23", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.23.tgz", - "integrity": "sha512-wBbys3Y53Ikly556vyADurKpYQHXS7Jjaskbz+Ga9PZCz7PB/9f3VdKbDlz7dqIzn+xwz7L/a6TR4iXcOi8IRw==", + "version": "3.996.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.24.tgz", + "integrity": "sha512-amP7tLikppN940wbBFISYqiuzVmpzMS9U3mcgtmVLjX4fdWI/SNCvrXv6ZxfVzTT4cT0rPKOLhFah2xLwzREWw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.35", + "@aws-sdk/middleware-sdk-s3": "^3.972.36", "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", @@ -2800,13 +2854,13 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.1038.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1038.0.tgz", - "integrity": "sha512-Qniru+9oGGb/HNK/gGZWbV3jsD0k71ngE7qMQ/x6gYNYLd2EOwHCS6E2E6jfkaqO4i0d+nNKmfRy8bNcshKdGQ==", + "version": "3.1039.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1039.0.tgz", + "integrity": "sha512-NMSFL2HwkAOoCeLCQiqoOq5pT3vVbSjww2QZTuYgYknVwhhv125PSDzZIcL5EYnlxuPWjEOdauZK+FspkZDVdw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.6", - "@aws-sdk/nested-clients": "^3.997.4", + "@aws-sdk/core": "^3.974.7", + "@aws-sdk/nested-clients": "^3.997.5", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", @@ -2898,12 +2952,12 @@ } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.22", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.22.tgz", - "integrity": "sha512-YTYqTmOUrwbm1h99Ee4y/mVYpFRl0oSO/amtP5cc1BZZWdaAVWs9zj3TkyRHWvR9aI/ZS8m3mS6awXtYUlWyaw==", + "version": "3.973.23", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.23.tgz", + "integrity": "sha512-gGwq8L2Euw0aNG6Ey4EktiAo3fSCVoDy1CaBIthd+oeaKHPXUrNaApMewQ6La5Hv0lcznOtECZaNvYyc5LXXfA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.36", + "@aws-sdk/middleware-user-agent": "^3.972.37", "@aws-sdk/types": "^3.973.8", "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", @@ -4104,6 +4158,7 @@ } }, "node_modules/@opentelemetry/exporter-metrics-otlp-http": { +<<<<<<< HEAD "version": "0.217.0", "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.217.0.tgz", "integrity": "sha512-1zkMzzhiNJdVmLxuwkltqWGw4fOOam47bqRxmuQNjyKJe/9NmY5cIrZ4kiQV7sVGxoOgT0ZvGUfLcjvtpC/b9Q==", @@ -4114,6 +4169,18 @@ "@opentelemetry/otlp-transformer": "0.217.0", "@opentelemetry/resources": "2.7.1", "@opentelemetry/sdk-metrics": "2.7.1" +======= + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.215.0.tgz", + "integrity": "sha512-FRydO5j7MWnXK9ghfykKxiSM8I5UeiicK/UNl3/mv86xoEKkb+LKz1I3WXgkuYVOQf22VNqbPO58s2W1mVWtEQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.0", + "@opentelemetry/otlp-exporter-base": "0.215.0", + "@opentelemetry/otlp-transformer": "0.215.0", + "@opentelemetry/resources": "2.7.0", + "@opentelemetry/sdk-metrics": "2.7.0" +>>>>>>> origin/main }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -4123,9 +4190,15 @@ } }, "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/api-logs": { +<<<<<<< HEAD "version": "0.217.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.217.0.tgz", "integrity": "sha512-Cdq0jW2lknrNfrAm92MyEAvpe2cRsKjdnQLHUL6xRA4IVUnsWx6P65E7NcUO0Y+L4w1Aee5iV8FvjSwd+lrs9A==", +======= + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.215.0.tgz", + "integrity": "sha512-xrFlqhdhUyO8wSRn6DjE0145/HPWSJ5Nm0C7vWua6TdL/FSEAZvEyvdsa9CRXuxo9ebb7j/NEPhEcO62IJ0qUA==", +>>>>>>> origin/main "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.3.0" @@ -4134,6 +4207,7 @@ "node": ">=8.0.0" } }, +<<<<<<< HEAD "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/otlp-transformer": { "version": "0.217.0", "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.217.0.tgz", @@ -4147,6 +4221,36 @@ "@opentelemetry/sdk-metrics": "2.7.1", "@opentelemetry/sdk-trace-base": "2.7.1", "protobufjs": "8.0.1" +======= + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.0.tgz", + "integrity": "sha512-DT12SXVwV2eoJrGf4nnsvZojxxeQo+LlNAsoYGRRObPWTeN6APiqZ2+nqDCQDvQX40eLi1AePONS0onoASp3yQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.215.0.tgz", + "integrity": "sha512-cWwBvaV+vkXHkSoTYR8hGw+AW03UlgTr6xtrUKOMeum3T+8vffYXIfXu6KY5MLu8O9QtoBKqaKWw9I5xoOepng==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.215.0", + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0", + "@opentelemetry/sdk-logs": "0.215.0", + "@opentelemetry/sdk-metrics": "2.7.0", + "@opentelemetry/sdk-trace-base": "2.7.0", + "protobufjs": "^8.0.1" +>>>>>>> origin/main }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -4155,6 +4259,7 @@ "@opentelemetry/api": "^1.3.0" } }, +<<<<<<< HEAD "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-logs": { "version": "0.217.0", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.217.0.tgz", @@ -4164,6 +4269,33 @@ "@opentelemetry/api-logs": "0.217.0", "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", +======= + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/resources": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.0.tgz", + "integrity": "sha512-K+oi0hNMv94EpZbnW3eyu2X6SGVpD3O5DhG2NIp65Hc7lhAj9brRXTAVzh3wB82+q3ThakEf7Zd7RsFUqcTc7A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-logs": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.215.0.tgz", + "integrity": "sha512-y3ucOmphzc4vgBTyIGchs+N/1rkACmoka8QalT2z1LBNM232Z17zMYayHcMl+dgMoOadZ0b72UZv7mDtqy1cFA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.215.0", + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0", +>>>>>>> origin/main "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -4173,6 +4305,7 @@ "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, +<<<<<<< HEAD "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", @@ -4181,6 +4314,32 @@ "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", +======= + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.0.tgz", + "integrity": "sha512-Vd7h95av/LYRsAVN7wbprvvJnHkq7swMXAo7Uad0Uxf9jl6NSReLa0JNivrcc5BVIx/vl2t+cgdVQQbnVhsR9w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.0.tgz", + "integrity": "sha512-Yg9zEXJB50DLVLpsKPk7NmNqlPlS+OvqhJGh0A8oawIOTPOwlm4eXs9BMJV7L79lvEwI+dWtAj+YjTyddV336A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0", +>>>>>>> origin/main "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -4190,7 +4349,22 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/protobufjs": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.2.0.tgz", + "integrity": "sha512-oI+GC9iPxrQEr6wragljFKH46/r3rNsm6eg7F2fp6kBUMnf6/mesDRdBuF4gK+OyaKJ8N4C1B9s9cCeYdqFikg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/@opentelemetry/otlp-exporter-base": { +<<<<<<< HEAD "version": "0.217.0", "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.217.0.tgz", "integrity": "sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ==", @@ -4198,6 +4372,15 @@ "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/otlp-transformer": "0.217.0" +======= + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.215.0.tgz", + "integrity": "sha512-lHrfbmeLSmesGSkkHiqDwOzfaEMSWXdc7q6UoLfbW8byONCb+bE/zkAr0kapN4US1baT/2nbpNT7Cn9XoB96Vg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.0", + "@opentelemetry/otlp-transformer": "0.215.0" +>>>>>>> origin/main }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -4207,9 +4390,15 @@ } }, "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/api-logs": { +<<<<<<< HEAD "version": "0.217.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.217.0.tgz", "integrity": "sha512-Cdq0jW2lknrNfrAm92MyEAvpe2cRsKjdnQLHUL6xRA4IVUnsWx6P65E7NcUO0Y+L4w1Aee5iV8FvjSwd+lrs9A==", +======= + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.215.0.tgz", + "integrity": "sha512-xrFlqhdhUyO8wSRn6DjE0145/HPWSJ5Nm0C7vWua6TdL/FSEAZvEyvdsa9CRXuxo9ebb7j/NEPhEcO62IJ0qUA==", +>>>>>>> origin/main "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "^1.3.0" @@ -4218,6 +4407,7 @@ "node": ">=8.0.0" } }, +<<<<<<< HEAD "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/otlp-transformer": { "version": "0.217.0", "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.217.0.tgz", @@ -4231,6 +4421,36 @@ "@opentelemetry/sdk-metrics": "2.7.1", "@opentelemetry/sdk-trace-base": "2.7.1", "protobufjs": "8.0.1" +======= + "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.0.tgz", + "integrity": "sha512-DT12SXVwV2eoJrGf4nnsvZojxxeQo+LlNAsoYGRRObPWTeN6APiqZ2+nqDCQDvQX40eLi1AePONS0onoASp3yQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/otlp-transformer": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.215.0.tgz", + "integrity": "sha512-cWwBvaV+vkXHkSoTYR8hGw+AW03UlgTr6xtrUKOMeum3T+8vffYXIfXu6KY5MLu8O9QtoBKqaKWw9I5xoOepng==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.215.0", + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0", + "@opentelemetry/sdk-logs": "0.215.0", + "@opentelemetry/sdk-metrics": "2.7.0", + "@opentelemetry/sdk-trace-base": "2.7.0", + "protobufjs": "^8.0.1" +>>>>>>> origin/main }, "engines": { "node": "^18.19.0 || >=20.6.0" @@ -4239,6 +4459,7 @@ "@opentelemetry/api": "^1.3.0" } }, +<<<<<<< HEAD "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/sdk-logs": { "version": "0.217.0", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.217.0.tgz", @@ -4248,6 +4469,33 @@ "@opentelemetry/api-logs": "0.217.0", "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", +======= + "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/resources": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.0.tgz", + "integrity": "sha512-K+oi0hNMv94EpZbnW3eyu2X6SGVpD3O5DhG2NIp65Hc7lhAj9brRXTAVzh3wB82+q3ThakEf7Zd7RsFUqcTc7A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/sdk-logs": { + "version": "0.215.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.215.0.tgz", + "integrity": "sha512-y3ucOmphzc4vgBTyIGchs+N/1rkACmoka8QalT2z1LBNM232Z17zMYayHcMl+dgMoOadZ0b72UZv7mDtqy1cFA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.215.0", + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0", +>>>>>>> origin/main "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -4257,6 +4505,7 @@ "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, +<<<<<<< HEAD "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/sdk-trace-base": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", @@ -4265,6 +4514,32 @@ "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/resources": "2.7.1", +======= + "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.0.tgz", + "integrity": "sha512-Vd7h95av/LYRsAVN7wbprvvJnHkq7swMXAo7Uad0Uxf9jl6NSReLa0JNivrcc5BVIx/vl2t+cgdVQQbnVhsR9w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.0.tgz", + "integrity": "sha512-Yg9zEXJB50DLVLpsKPk7NmNqlPlS+OvqhJGh0A8oawIOTPOwlm4eXs9BMJV7L79lvEwI+dWtAj+YjTyddV336A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.0", + "@opentelemetry/resources": "2.7.0", +>>>>>>> origin/main "@opentelemetry/semantic-conventions": "^1.29.0" }, "engines": { @@ -4274,6 +4549,20 @@ "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, + "node_modules/@opentelemetry/otlp-exporter-base/node_modules/protobufjs": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.2.0.tgz", + "integrity": "sha512-oI+GC9iPxrQEr6wragljFKH46/r3rNsm6eg7F2fp6kBUMnf6/mesDRdBuF4gK+OyaKJ8N4C1B9s9cCeYdqFikg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/@opentelemetry/otlp-transformer": { "version": "0.213.0", "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.213.0.tgz", @@ -4518,6 +4807,73 @@ "node": ">=18" } }, +<<<<<<< HEAD +======= + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz", + "integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "license": "BSD-3-Clause" + }, +>>>>>>> origin/main "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-rc.17", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", @@ -4906,9 +5262,9 @@ "license": "MIT" }, "node_modules/@secretlint/secretlint-rule-preset-recommend": { - "version": "12.3.1", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-12.3.1.tgz", - "integrity": "sha512-w9x9rIP1+qhV0k9k15aSIBo+6NrM4npAYIoNs7UmKIxuQSA6b91rhWpdMxgCv2/EQtym+gpuyN7rTSvG5wlYlw==", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-12.2.0.tgz", + "integrity": "sha512-n4qknL6vYRelmyrAyV/Z8I85c6jS6yF/ZxpgcqebjJuECIiel8OT2wIVIq9vk0MwlQN35skaQu0KvfM8uuGeyA==", "dev": true, "license": "MIT", "engines": { @@ -5680,24 +6036,24 @@ "license": "MIT" }, "node_modules/@textlint/ast-node-types": { - "version": "15.6.0", - "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.6.0.tgz", - "integrity": "sha512-CxZHFbYAU7J0A4izz31wV2ZZfySR6aVj2OSR6/3tppZm7VV6hM7nA7sutsLwIiBL/v4lsB1RM79l4Dc/VrH4qw==", + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.6.1.tgz", + "integrity": "sha512-KXUhbpBctWkSumyNwFQBufwWCcjdxARGVnlH6AfERaFAnpi300L8og+l2Hs/bIqkXu26T5tIs7jNnRgUs20hmQ==", "dev": true, "license": "MIT" }, "node_modules/@textlint/linter-formatter": { - "version": "15.6.0", - "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.6.0.tgz", - "integrity": "sha512-IwHRhjwxs0a5t1eNAoKAdV224CDca38LyopPofXpwO/d0J75wBvzf/cBHXNl4TMsLKhYGtR83UprcLEKj/gZsA==", + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.6.1.tgz", + "integrity": "sha512-mrLdBQkySnUsznPCfOuzyZk3381bJoaK2hPzwmRvOqUeRm04SdPEqN3rkqnbeB4Vw61d1z6gM+kJSS0y8+Zmog==", "dev": true, "license": "MIT", "dependencies": { "@azu/format-text": "^1.0.2", "@azu/style-format": "^1.0.1", - "@textlint/module-interop": "15.6.0", - "@textlint/resolver": "15.6.0", - "@textlint/types": "15.6.0", + "@textlint/module-interop": "15.6.1", + "@textlint/resolver": "15.6.1", + "@textlint/types": "15.6.1", "chalk": "^4.1.2", "debug": "^4.4.3", "js-yaml": "^4.1.1", @@ -5740,27 +6096,27 @@ } }, "node_modules/@textlint/module-interop": { - "version": "15.6.0", - "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.6.0.tgz", - "integrity": "sha512-MHY6pJx9i5kOlrvUSK51887tYZjHAV2qnr6unBm7LtBLGDFo93utdYqHyWep8r9QLsilQdeijWtufJI46z4v4w==", + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.6.1.tgz", + "integrity": "sha512-hrsG9S7dlTPoFQ9ImKCHt1I7uPGt25lBXoc/ENP0U47tETAsg8mwjwHBQ4SEEH/X+AXYIEg6+saPyKAj08Aa+Q==", "dev": true, "license": "MIT" }, "node_modules/@textlint/resolver": { - "version": "15.6.0", - "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.6.0.tgz", - "integrity": "sha512-T1l2Gd3455pwtm0cTewhX/LLy3bL9z6/Fu/am+jj+jjGfXVoknYkjfkZEKrjHlA7xzay0EfUKnu//teYemLeZw==", + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.6.1.tgz", + "integrity": "sha512-kC8MFJH9mIoTtw1IPupsl9k2KF/xj48ZqJoEfVmAqJ2yqd7gvvNRAgndOIBTZOLHDyFK1ouHpFPhW3ID/kNcmw==", "dev": true, "license": "MIT" }, "node_modules/@textlint/types": { - "version": "15.6.0", - "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.6.0.tgz", - "integrity": "sha512-CvgYb1PiqF4BGyoZebGWzAJCZ4ChJAZ9gtWjpQIMKE4Xe2KlSwDA8m8MsiZIV321f5Ibx38BMjC1Z/2ZYP2GQg==", + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.6.1.tgz", + "integrity": "sha512-dRdyTAMRaqcU5eHpJWgTq9kcKGErs+cVVFwNTP3gUAFDJxEVxdOlJU2zjMgzJAzf/4WfOE6UJ56Mmn09acxmzw==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/ast-node-types": "15.6.0" + "@textlint/ast-node-types": "15.6.1" } }, "node_modules/@trivago/prettier-plugin-sort-imports": { @@ -6996,9 +7352,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.251.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.251.0.tgz", - "integrity": "sha512-H1Jfz2Oyejn+yG24i+By9fZpYfg+E3h1XnFCF2wnt/MyGOTIePRph7MRGkX73ap10ERSpmd0Ly58OLVykFoSQA==", + "version": "2.250.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.250.0.tgz", + "integrity": "sha512-8U8/S9VcmKSc3MHZWiB7P0IecgXoohI8Ya3dgtZMgbzC4mB+MEQmsYBeNgm4vzGQdRos8HjQLnFX1IBlZh7jQA==", "bundleDependencies": [ "@balena/dockerignore", "@aws-cdk/cloud-assembly-api", @@ -7018,8 +7374,8 @@ "dependencies": { "@aws-cdk/asset-awscli-v1": "2.2.273", "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.1", - "@aws-cdk/cloud-assembly-api": "^2.2.2", - "@aws-cdk/cloud-assembly-schema": "^53.18.0", + "@aws-cdk/cloud-assembly-api": "^2.2.0", + "@aws-cdk/cloud-assembly-schema": "^53.0.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", "fs-extra": "^11.3.3", @@ -7040,7 +7396,7 @@ } }, "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api": { - "version": "2.2.2", + "version": "2.2.0", "bundleDependencies": [ "jsonschema", "semver" @@ -7056,7 +7412,7 @@ "node": ">= 18.0.0" }, "peerDependencies": { - "@aws-cdk/cloud-assembly-schema": ">=53.15.0" + "@aws-cdk/cloud-assembly-schema": ">=53.0.0" } }, "node_modules/aws-cdk-lib/node_modules/@aws-cdk/cloud-assembly-api/node_modules/jsonschema": { @@ -7808,9 +8164,9 @@ } }, "node_modules/cdk-from-cfn": { - "version": "0.297.0", - "resolved": "https://registry.npmjs.org/cdk-from-cfn/-/cdk-from-cfn-0.297.0.tgz", - "integrity": "sha512-ZyiugKPe9QYmfXNwbjBhc8sgbos7E0mQfo9P3/vrID8iKTsf5YZswPDu526sBTg4Fpx94N08UJZjLepRxaK4Ng==", + "version": "0.295.0", + "resolved": "https://registry.npmjs.org/cdk-from-cfn/-/cdk-from-cfn-0.295.0.tgz", + "integrity": "sha512-HNQu3TfNTHZNlxh/o0XxhMMSt3uDFDtMxxO2wZGvZpHwvjZLLFSCHooMbMGj75vtyqNmqKxQdR9WQSTcW3oIpg==", "license": "MIT OR Apache-2.0" }, "node_modules/chai": { @@ -13377,9 +13733,15 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { +<<<<<<< HEAD "version": "11.3.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", +======= + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", +>>>>>>> origin/main "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -13599,12 +13961,31 @@ } }, "node_modules/protobufjs": { +<<<<<<< HEAD "version": "8.0.3", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.0.3.tgz", "integrity": "sha512-LBYnMWkKLB8fE/ljROPDbCl7mgLSlI+oBe1fAAr5MTqFg4TIi0tYrVVurJvQggOjnUYMQtEZBjrej59ojMNTHQ==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { +======= + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.8.tgz", + "integrity": "sha512-dvpCIeLPbXZS/Ete7yLaO7RenOdken2NHKykBXbsaGxZT0UTltcarBciw+A78SRQs9iMAAVpsYA+l8b1hTePIA==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.1", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", +>>>>>>> origin/main "@types/node": ">=13.7.0", "long": "^5.0.0" }, diff --git a/package.json b/package.json index 2516faaab..ce2cd1759 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,10 @@ { "name": "@aws/agentcore", +<<<<<<< HEAD "version": "1.0.0-preview.8", +======= + "version": "0.13.1", +>>>>>>> origin/main "description": "CLI for Amazon Bedrock AgentCore", "license": "Apache-2.0", "repository": { @@ -78,7 +82,7 @@ "@aws-sdk/client-bedrock": "^3.1012.0", "@aws-sdk/client-bedrock-agent": "^3.1012.0", "@aws-sdk/client-bedrock-agentcore": "^3.1020.0", - "@aws-sdk/client-bedrock-agentcore-control": "^3.1020.0", + "@aws-sdk/client-bedrock-agentcore-control": "^3.1039.0", "@aws-sdk/client-bedrock-runtime": "^3.893.0", "@aws-sdk/client-cloudformation": "^3.893.0", "@aws-sdk/client-cloudwatch-logs": "^3.893.0", @@ -90,7 +94,11 @@ "@aws/agent-inspector": "0.3.0", "@commander-js/extra-typings": "^14.0.0", "@opentelemetry/api": "^1.9.1", +<<<<<<< HEAD "@opentelemetry/exporter-metrics-otlp-http": "^0.217.0", +======= + "@opentelemetry/exporter-metrics-otlp-http": "^0.215.0", +>>>>>>> origin/main "@opentelemetry/otlp-transformer": "^0.213.0", "@opentelemetry/resources": "^2.6.1", "@opentelemetry/sdk-metrics": "^2.6.1", diff --git a/schemas/agentcore.schema.v1.json b/schemas/agentcore.schema.v1.json index 1b7551d54..c6e9caef0 100644 --- a/schemas/agentcore.schema.v1.json +++ b/schemas/agentcore.schema.v1.json @@ -1845,6 +1845,7 @@ "additionalProperties": false } }, +<<<<<<< HEAD "harnesses": { "default": [], "type": "array", @@ -1866,6 +1867,8 @@ "additionalProperties": false } }, +======= +>>>>>>> origin/main "configBundles": { "default": [], "type": "array", diff --git a/src/cli/AGENTS.md b/src/cli/AGENTS.md index 55512bb47..9c9a3a921 100644 --- a/src/cli/AGENTS.md +++ b/src/cli/AGENTS.md @@ -82,7 +82,7 @@ primitives/ ├── registry.ts # Singleton instances + ALL_PRIMITIVES array ├── credential-utils.ts # Shared credential env var name computation ├── constants.ts # SOURCE_CODE_NOTE and other shared constants -├── types.ts # AddResult, RemovableResource, RemovalResult, etc. +├── types.ts # RemovableResource, AddScreenComponent, etc. └── index.ts # Barrel exports ``` @@ -92,8 +92,8 @@ Every primitive extends `BasePrimitive` and implements: - `kind` — resource identifier (`'agent'`, `'memory'`, `'identity'`, `'gateway'`, `'mcp-tool'`) - `label` — human-readable name (`'Agent'`, `'Memory'`, `'Identity'`) -- `add(options)` — create a resource, returns `AddResult` -- `remove(name)` — remove a resource, returns `RemovalResult` +- `add(options)` — create a resource, returns `Result` +- `remove(name)` — remove a resource, returns `Result` - `previewRemove(name)` — preview what removal will do - `getRemovable()` — list resources available for removal - `registerCommands(addCmd, removeCmd)` — register CLI subcommands @@ -119,7 +119,8 @@ BasePrimitive provides shared helpers: - **Absorb, don't wrap.** Each primitive owns its logic directly. Do not create facade files that delegate to primitives. - **No backward-compatibility shims.** This is a CLI, not a library. If the CLI functions the same, delete old files. -- **Use `{ success, error? }` result format** throughout (never `{ ok, error }`). See `AddResult` and `RemovalResult`. +- **Use the discriminated `Result` union** from `src/lib/result.ts` throughout. See typed error classes in + `src/lib/errors/types.ts`. - **Dynamic imports for ink/React only.** TUI components (ink, react, screen components) must be dynamically imported inside Commander action handlers to prevent esbuild async module propagation issues. All other imports go at the top of the file. See the esbuild section below. diff --git a/src/cli/__tests__/errors.test.ts b/src/cli/__tests__/errors.test.ts index 4a8d2c1cf..663cd3508 100644 --- a/src/cli/__tests__/errors.test.ts +++ b/src/cli/__tests__/errors.test.ts @@ -1,5 +1,5 @@ +import { AgentAlreadyExistsError, toError } from '../../lib'; import { - AgentAlreadyExistsError, getErrorMessage, isChangesetInProgressError, isExpiredTokenError, @@ -204,4 +204,35 @@ describe('errors', () => { expect(isChangesetInProgressError({})).toBe(false); }); }); + + describe('toError', () => { + it('returns Error instance as-is', () => { + const err = new Error('original'); + expect(toError(err)).toBe(err); + }); + + it('wraps string in Error', () => { + const result = toError('string error'); + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe('string error'); + }); + + it('handles null', () => { + const result = toError(null); + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe('null'); + }); + + it('handles undefined', () => { + const result = toError(undefined); + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe('undefined'); + }); + + it('handles non-Error objects', () => { + const result = toError({ code: 42 }); + expect(result).toBeInstanceOf(Error); + expect(result.message).toBe('[object Object]'); + }); + }); }); diff --git a/src/cli/__tests__/update-notifier.test.ts b/src/cli/__tests__/update-notifier.test.ts index 27eb3f649..3756271df 100644 --- a/src/cli/__tests__/update-notifier.test.ts +++ b/src/cli/__tests__/update-notifier.test.ts @@ -1,6 +1,14 @@ +import { ONE_DAY_MS, ONE_HOUR_MS, ONE_SECOND_MS } from '../../lib/time-constants.js'; +import * as action from '../commands/update/action.js'; +import * as constants from '../constants.js'; import { type UpdateCheckResult, checkForUpdate, printUpdateNotification } from '../update-notifier.js'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'fs'; +import { mkdir, readFile, writeFile } from 'fs/promises'; +import { tmpdir } from 'os'; +import { join } from 'path'; +import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +<<<<<<< HEAD const { mockReadFile, mockWriteFile, mockMkdir } = vi.hoisted(() => ({ mockReadFile: vi.fn(), mockWriteFile: vi.fn(), @@ -32,82 +40,78 @@ vi.mock('../commands/update/action.js', () => ({ fetchLatestVersion: mockFetchLatestVersion, compareVersions: mockCompareVersions, })); +======= +const NOW = 1708646400000; +const tmpDir = mkdtempSync(join(tmpdir(), 'update-notifier-test-')); +const CACHE_FILE = join(tmpDir, 'update-check.json'); +>>>>>>> origin/main describe('checkForUpdate', () => { + let originalConfigDir: string | undefined; + beforeEach(() => { - vi.spyOn(Date, 'now').mockReturnValue(1708646400000); - mockWriteFile.mockResolvedValue(undefined); - mockMkdir.mockResolvedValue(undefined); + originalConfigDir = process.env.AGENTCORE_CONFIG_DIR; + process.env.AGENTCORE_CONFIG_DIR = tmpDir; + vi.spyOn(Date, 'now').mockReturnValue(NOW); + vi.spyOn(constants, 'PACKAGE_VERSION', 'get').mockReturnValue('1.0.0'); + rmSync(tmpDir, { recursive: true, force: true }); }); afterEach(() => { vi.restoreAllMocks(); - mockReadFile.mockReset(); - mockWriteFile.mockReset(); - mockMkdir.mockReset(); - mockFetchLatestVersion.mockReset(); - mockCompareVersions.mockReset(); + if (originalConfigDir === undefined) { + delete process.env.AGENTCORE_CONFIG_DIR; + } else { + process.env.AGENTCORE_CONFIG_DIR = originalConfigDir; + } + }); + + afterAll(() => { + rmSync(tmpDir, { recursive: true, force: true }); }); it('fetches from registry when no cache exists', async () => { - mockReadFile.mockRejectedValue(new Error('ENOENT')); - mockFetchLatestVersion.mockResolvedValue('2.0.0'); - mockCompareVersions.mockReturnValue(1); + vi.spyOn(action, 'fetchLatestVersion').mockResolvedValue('2.0.0'); const result = await checkForUpdate(); expect(result).toEqual({ updateAvailable: true, latestVersion: '2.0.0' }); - expect(mockFetchLatestVersion).toHaveBeenCalled(); }); it('uses cache when last check was less than 24 hours ago', async () => { - const cache = JSON.stringify({ - lastCheck: 1708646400000 - 1000, // 1 second ago - latestVersion: '2.0.0', - }); - mockReadFile.mockResolvedValue(cache); - mockCompareVersions.mockReturnValue(1); + await mkdir(tmpDir, { recursive: true }); + await writeFile(CACHE_FILE, JSON.stringify({ lastCheck: NOW - ONE_SECOND_MS, latestVersion: '2.0.0' }), 'utf-8'); const result = await checkForUpdate(); expect(result).toEqual({ updateAvailable: true, latestVersion: '2.0.0' }); - expect(mockFetchLatestVersion).not.toHaveBeenCalled(); }); it('fetches from registry when cache is expired', async () => { - const cache = JSON.stringify({ - lastCheck: 1708646400000 - 25 * 60 * 60 * 1000, // 25 hours ago - latestVersion: '1.5.0', - }); - mockReadFile.mockResolvedValue(cache); - mockFetchLatestVersion.mockResolvedValue('2.0.0'); - mockCompareVersions.mockReturnValue(1); + await mkdir(tmpDir, { recursive: true }); + await writeFile( + CACHE_FILE, + JSON.stringify({ lastCheck: NOW - ONE_DAY_MS - ONE_HOUR_MS, latestVersion: '1.5.0' }), + 'utf-8' + ); + vi.spyOn(action, 'fetchLatestVersion').mockResolvedValue('2.0.0'); const result = await checkForUpdate(); expect(result).toEqual({ updateAvailable: true, latestVersion: '2.0.0' }); - expect(mockFetchLatestVersion).toHaveBeenCalled(); }); it('writes cache after fetching', async () => { - mockReadFile.mockRejectedValue(new Error('ENOENT')); - mockFetchLatestVersion.mockResolvedValue('2.0.0'); - mockCompareVersions.mockReturnValue(1); + vi.spyOn(action, 'fetchLatestVersion').mockResolvedValue('2.0.0'); await checkForUpdate(); - expect(mockMkdir).toHaveBeenCalled(); - expect(mockWriteFile).toHaveBeenCalledWith( - expect.stringContaining('update-check.json'), - JSON.stringify({ lastCheck: 1708646400000, latestVersion: '2.0.0' }), - 'utf-8' - ); + const cached = JSON.parse(await readFile(CACHE_FILE, 'utf-8')); + expect(cached).toEqual({ lastCheck: NOW, latestVersion: '2.0.0' }); }); it('returns updateAvailable: false when versions match', async () => { - mockReadFile.mockRejectedValue(new Error('ENOENT')); - mockFetchLatestVersion.mockResolvedValue('1.0.0'); - mockCompareVersions.mockReturnValue(0); + vi.spyOn(action, 'fetchLatestVersion').mockResolvedValue('1.0.0'); const result = await checkForUpdate(); @@ -115,9 +119,7 @@ describe('checkForUpdate', () => { }); it('returns updateAvailable: false when current is newer', async () => { - mockReadFile.mockRejectedValue(new Error('ENOENT')); - mockFetchLatestVersion.mockResolvedValue('0.9.0'); - mockCompareVersions.mockReturnValue(-1); + vi.spyOn(action, 'fetchLatestVersion').mockResolvedValue('0.9.0'); const result = await checkForUpdate(); @@ -125,8 +127,7 @@ describe('checkForUpdate', () => { }); it('returns null on fetch error', async () => { - mockReadFile.mockRejectedValue(new Error('ENOENT')); - mockFetchLatestVersion.mockRejectedValue(new Error('network error')); + vi.spyOn(action, 'fetchLatestVersion').mockRejectedValue(new Error('network error')); const result = await checkForUpdate(); @@ -134,8 +135,9 @@ describe('checkForUpdate', () => { }); it('returns null on cache parse error and fetch error', async () => { - mockReadFile.mockResolvedValue('invalid json'); - mockFetchLatestVersion.mockRejectedValue(new Error('network error')); + await mkdir(tmpDir, { recursive: true }); + await writeFile(CACHE_FILE, 'invalid json', 'utf-8'); + vi.spyOn(action, 'fetchLatestVersion').mockRejectedValue(new Error('network error')); const result = await checkForUpdate(); @@ -143,10 +145,14 @@ describe('checkForUpdate', () => { }); it('succeeds even when cache write fails', async () => { - mockReadFile.mockRejectedValue(new Error('ENOENT')); - mockFetchLatestVersion.mockResolvedValue('2.0.0'); - mockCompareVersions.mockReturnValue(1); - mockWriteFile.mockRejectedValue(new Error('EACCES')); + // Point config dir at a regular file — mkdir/writeFile will fail because + // a file can't be used as a directory. Works cross-platform and as root. + mkdirSync(tmpDir, { recursive: true }); + const blocker = join(tmpDir, 'not-a-dir'); + writeFileSync(blocker, ''); + process.env.AGENTCORE_CONFIG_DIR = blocker; + + vi.spyOn(action, 'fetchLatestVersion').mockResolvedValue('2.0.0'); const result = await checkForUpdate(); @@ -163,7 +169,6 @@ describe('printUpdateNotification', () => { const output = stderrSpy.mock.calls.map(c => c[0]).join(''); expect(output).toContain('Update available:'); - expect(output).toContain('1.0.0'); expect(output).toContain('2.0.0'); expect(output).toContain('npm install -g @aws/agentcore@latest'); diff --git a/src/cli/aws/__tests__/transaction-search.test.ts b/src/cli/aws/__tests__/transaction-search.test.ts index 67ccd0ac6..307063a61 100644 --- a/src/cli/aws/__tests__/transaction-search.test.ts +++ b/src/cli/aws/__tests__/transaction-search.test.ts @@ -1,4 +1,5 @@ import { enableTransactionSearch } from '../transaction-search.js'; +import assert from 'node:assert'; import { beforeEach, describe, expect, it, vi } from 'vitest'; const { mockAppSignalsSend, mockLogsSend, mockXRaySend } = vi.hoisted(() => ({ @@ -162,8 +163,8 @@ describe('enableTransactionSearch', () => { const result = await enableTransactionSearch('us-east-1', '123456789012'); - expect(result.success).toBe(false); - expect(result.error).toContain('Insufficient permissions to enable Application Signals'); + assert(!result.success); + expect(result.error.message).toContain('Insufficient permissions to enable Application Signals'); }); it('returns error when Application Signals fails with generic error', async () => { @@ -171,8 +172,8 @@ describe('enableTransactionSearch', () => { const result = await enableTransactionSearch('us-east-1', '123456789012'); - expect(result.success).toBe(false); - expect(result.error).toContain('Failed to enable Application Signals'); + assert(!result.success); + expect(result.error.message).toContain('Failed to enable Application Signals'); }); it('returns error when CloudWatch Logs policy fails with AccessDenied', async () => { @@ -183,8 +184,8 @@ describe('enableTransactionSearch', () => { const result = await enableTransactionSearch('us-east-1', '123456789012'); - expect(result.success).toBe(false); - expect(result.error).toContain('Insufficient permissions to configure CloudWatch Logs policy'); + assert(!result.success); + expect(result.error.message).toContain('Insufficient permissions to configure CloudWatch Logs policy'); }); it('returns error when trace destination fails', async () => { @@ -194,8 +195,8 @@ describe('enableTransactionSearch', () => { const result = await enableTransactionSearch('us-east-1', '123456789012'); - expect(result.success).toBe(false); - expect(result.error).toContain('Failed to configure trace destination'); + assert(!result.success); + expect(result.error.message).toContain('Failed to configure trace destination'); }); it('returns error when indexing rule update fails', async () => { @@ -214,8 +215,8 @@ describe('enableTransactionSearch', () => { const result = await enableTransactionSearch('us-east-1', '123456789012'); - expect(result.success).toBe(false); - expect(result.error).toContain('Failed to configure indexing rules'); + assert(!result.success); + expect(result.error.message).toContain('Failed to configure indexing rules'); }); it('does not proceed to later steps when an earlier step fails', async () => { diff --git a/src/cli/aws/agentcore-control.ts b/src/cli/aws/agentcore-control.ts index d2770bacf..8a8569cf0 100644 --- a/src/cli/aws/agentcore-control.ts +++ b/src/cli/aws/agentcore-control.ts @@ -469,6 +469,7 @@ export interface GetEvaluatorResult { llmAsAJudge?: GetEvaluatorLlmConfig; codeBased?: GetEvaluatorCodeBasedConfig; }; + kmsKeyArn?: string; tags?: Record; } @@ -547,6 +548,7 @@ export async function getEvaluator(options: GetEvaluatorOptions): Promise { +): Promise { const credentials = getCredentialProvider(); // Step 1: Enable Application Signals (creates service-linked role, idempotent) @@ -44,9 +41,14 @@ export async function enableTransactionSearch( } catch (err: unknown) { const message = getErrorMessage(err); if (isAccessDeniedError(err)) { - return { success: false, error: `Insufficient permissions to enable Application Signals: ${message}` }; + return { + success: false, + error: new AccessDeniedError(`Insufficient permissions to enable Application Signals: ${message}`, { + cause: err, + }), + }; } - return { success: false, error: `Failed to enable Application Signals: ${message}` }; + return { success: false, error: new Error(`Failed to enable Application Signals: ${message}`, { cause: err }) }; } // Step 2: Create CloudWatch Logs resource policy for X-Ray (if needed) @@ -80,9 +82,17 @@ export async function enableTransactionSearch( } catch (err: unknown) { const message = getErrorMessage(err); if (isAccessDeniedError(err)) { - return { success: false, error: `Insufficient permissions to configure CloudWatch Logs policy: ${message}` }; + return { + success: false, + error: new AccessDeniedError(`Insufficient permissions to configure CloudWatch Logs policy: ${message}`, { + cause: err, + }), + }; } - return { success: false, error: `Failed to configure CloudWatch Logs policy: ${message}` }; + return { + success: false, + error: new Error(`Failed to configure CloudWatch Logs policy: ${message}`, { cause: err }), + }; } const xrayClient = new XRayClient({ region, credentials }); @@ -96,9 +106,14 @@ export async function enableTransactionSearch( } catch (err: unknown) { const message = getErrorMessage(err); if (isAccessDeniedError(err)) { - return { success: false, error: `Insufficient permissions to configure trace destination: ${message}` }; + return { + success: false, + error: new AccessDeniedError(`Insufficient permissions to configure trace destination: ${message}`, { + cause: err, + }), + }; } - return { success: false, error: `Failed to configure trace destination: ${message}` }; + return { success: false, error: new Error(`Failed to configure trace destination: ${message}`, { cause: err }) }; } // Step 4: Set indexing to 100% on the built-in Default rule (always exists, idempotent) @@ -112,9 +127,14 @@ export async function enableTransactionSearch( } catch (err: unknown) { const message = getErrorMessage(err); if (isAccessDeniedError(err)) { - return { success: false, error: `Insufficient permissions to configure indexing rules: ${message}` }; + return { + success: false, + error: new AccessDeniedError(`Insufficient permissions to configure indexing rules: ${message}`, { + cause: err, + }), + }; } - return { success: false, error: `Failed to configure indexing rules: ${message}` }; + return { success: false, error: new Error(`Failed to configure indexing rules: ${message}`, { cause: err }) }; } return { success: true }; diff --git a/src/cli/cli.ts b/src/cli/cli.ts index f4e56a916..8b256d678 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -1,7 +1,10 @@ import { getOrCreateInstallationId } from '../lib/schemas/io/global-config'; import { registerABTestCommand } from './commands/abtest'; import { registerAdd } from './commands/add'; +<<<<<<< HEAD import { registerAddTool } from './commands/add/tool-command'; +======= +>>>>>>> origin/main import { registerArchive } from './commands/archive'; import { registerConfigBundle } from './commands/config-bundle'; import { registerCreate } from './commands/create'; @@ -208,10 +211,13 @@ export function registerCommands(program: Command) { primitive.registerCommands(addCmd, removeCmd); } +<<<<<<< HEAD // Register standalone add/remove subcommands registerAddTool(addCmd); registerRemoveTool(removeCmd); +======= +>>>>>>> origin/main // Register AB test detail command registerABTestCommand(program); } diff --git a/src/cli/commands/add/types.ts b/src/cli/commands/add/types.ts index 47e7c0ad9..cbf92ed63 100644 --- a/src/cli/commands/add/types.ts +++ b/src/cli/commands/add/types.ts @@ -41,13 +41,6 @@ export interface AddAgentOptions extends VpcOptions { json?: boolean; } -export interface AddAgentResult { - success: boolean; - agentName?: string; - agentPath?: string; - error?: string; -} - // Gateway types export interface AddGatewayOptions { name?: string; @@ -68,12 +61,6 @@ export interface AddGatewayOptions { json?: boolean; } -export interface AddGatewayResult { - success: boolean; - gatewayName?: string; - error?: string; -} - // Gateway Target types export interface AddGatewayTargetOptions { name?: string; @@ -100,6 +87,7 @@ export interface AddGatewayTargetOptions { json?: boolean; } +<<<<<<< HEAD export interface AddGatewayTargetResult { success: boolean; toolName?: string; @@ -145,6 +133,8 @@ export interface AddHarnessCliOptions { json?: boolean; } +======= +>>>>>>> origin/main // Memory types (v2: no owner/user concept) export interface AddMemoryOptions { name?: string; @@ -157,12 +147,6 @@ export interface AddMemoryOptions { json?: boolean; } -export interface AddMemoryResult { - success: boolean; - memoryName?: string; - error?: string; -} - // Credential types (v2: credential, no owner/user concept) export interface AddCredentialOptions { name?: string; @@ -177,9 +161,3 @@ export interface AddCredentialOptions { /** @deprecated Use AddCredentialOptions */ export type AddIdentityOptions = AddCredentialOptions; - -export interface AddCredentialResult { - success: boolean; - credentialName?: string; - error?: string; -} diff --git a/src/cli/commands/create/action.ts b/src/cli/commands/create/action.ts index a00397f38..0f97de294 100644 --- a/src/cli/commands/create/action.ts +++ b/src/cli/commands/create/action.ts @@ -1,4 +1,13 @@ -import { APP_DIR, CONFIG_DIR, ConfigIO, setEnvVar, setSessionProjectRoot } from '../../../lib'; +import { + APP_DIR, + CONFIG_DIR, + ConfigIO, + DependencyCheckError, + GitInitError, + setEnvVar, + setSessionProjectRoot, + toError, +} from '../../../lib'; import type { BuildType, DeployedState, @@ -8,7 +17,6 @@ import type { SDKFramework, TargetLanguage, } from '../../../schema'; -import { getErrorMessage } from '../../errors'; import { checkCreateDependencies } from '../../external-requirements'; import { initGitRepo, setupPythonProject, writeEnvFile, writeGitignore } from '../../operations'; import { createConfigBundleForAgent } from '../../operations/agent/config-bundle-defaults'; @@ -57,7 +65,11 @@ export async function createProject(options: CreateProjectOptions): Promise 0 ? depWarnings : undefined, + }; } } @@ -93,7 +105,11 @@ export async function createProject(options: CreateProjectOptions): Promise 0 ? depWarnings : undefined, + }; } onProgress?.('Initialize git repository', 'done'); } @@ -104,7 +120,7 @@ export async function createProject(options: CreateProjectOptions): Promise 0 ? depWarnings : undefined, }; } catch (err) { - return { success: false, error: getErrorMessage(err), warnings: depWarnings }; + return { success: false, error: toError(err), warnings: depWarnings.length > 0 ? depWarnings : undefined }; } } @@ -174,7 +190,11 @@ export async function createProjectWithAgent(options: CreateWithAgentOptions): P // Fail on errors if (!depCheck.passed) { - return { success: false, error: depCheck.errors.join('\n'), warnings: depWarnings }; + return { + success: false, + error: new DependencyCheckError(depCheck.errors), + warnings: depWarnings.length > 0 ? depWarnings : undefined, + }; } // First create the base project (skip dependency check since we already did it) @@ -207,7 +227,11 @@ export async function createProjectWithAgent(options: CreateWithAgentOptions): P }); if (!importResult.success) { onProgress?.('Import agent from Bedrock', 'error'); - return { success: false, error: importResult.error, warnings: depWarnings }; + return { + success: false, + error: importResult.error, + warnings: depWarnings.length > 0 ? depWarnings : undefined, + }; } onProgress?.('Import agent from Bedrock', 'done'); return { @@ -217,7 +241,7 @@ export async function createProjectWithAgent(options: CreateWithAgentOptions): P warnings: depWarnings.length > 0 ? depWarnings : undefined, }; } catch (err) { - return { success: false, error: getErrorMessage(err), warnings: depWarnings }; + return { success: false, error: toError(err), warnings: depWarnings.length > 0 ? depWarnings : undefined }; } } @@ -310,7 +334,7 @@ export async function createProjectWithAgent(options: CreateWithAgentOptions): P warnings: depWarnings.length > 0 ? depWarnings : undefined, }; } catch (err) { - return { success: false, error: getErrorMessage(err), warnings: depWarnings }; + return { success: false, error: toError(err), warnings: depWarnings.length > 0 ? depWarnings : undefined }; } } diff --git a/src/cli/commands/create/command.tsx b/src/cli/commands/create/command.tsx index 9be7e7bf0..57ef36445 100644 --- a/src/cli/commands/create/command.tsx +++ b/src/cli/commands/create/command.tsx @@ -1,4 +1,4 @@ -import { getWorkingDirectory } from '../../../lib'; +import { getWorkingDirectory, serializeResult } from '../../../lib'; import type { BuildType, HarnessModelProvider, @@ -10,7 +10,22 @@ import type { } from '../../../schema'; import { LIFECYCLE_TIMEOUT_MAX, LIFECYCLE_TIMEOUT_MIN } from '../../../schema'; import { getErrorMessage } from '../../errors'; +<<<<<<< HEAD import { harnessPrimitive } from '../../primitives/registry'; +======= +import { runCliCommand } from '../../telemetry/cli-command-run.js'; +import { + AgentType, + Build, + Framework, + Language, + Memory, + ModelProvider as ModelProviderEnum, + NetworkMode as NetworkModeEnum, + Protocol, + standardize, +} from '../../telemetry/schemas/common-shapes.js'; +>>>>>>> origin/main import { COMMAND_DESCRIPTIONS } from '../../tui/copy'; import { requireTTY } from '../../tui/guards'; import { CreateScreen } from '../../tui/screens/create'; @@ -213,22 +228,25 @@ async function handleCreateAgentCLI(options: CreateOptions): Promise { const name = options.name ?? options.projectName; const projectName = options.projectName ?? name; - const validation = validateCreateOptions(options, cwd); - if (!validation.valid) { - if (options.json) { - console.log(JSON.stringify({ success: false, error: validation.error })); - } else { - console.error(validation.error); - } - process.exit(1); - } - - // Handle dry-run mode + // Handle dry-run mode (no telemetry for dry-run) if (options.dryRun) { +<<<<<<< HEAD const result = getDryRunInfo({ name: name!, projectName: projectName!, cwd, language: options.language }); +======= + const validation = validateCreateOptions(options, cwd); + if (!validation.valid) { + if (options.json) { + console.log(JSON.stringify({ success: false, error: validation.error })); + } else { + console.error(validation.error); + } + process.exit(1); + } + const result = getDryRunInfo({ name: name!, projectName, cwd, language: options.language }); +>>>>>>> origin/main if (options.json) { - console.log(JSON.stringify(result)); - } else { + console.log(JSON.stringify(serializeResult(result))); + } else if (result.success) { console.log('Dry run - would create:'); for (const path of result.wouldCreate ?? []) { console.log(` ${path}`); @@ -237,21 +255,96 @@ async function handleCreateAgentCLI(options: CreateOptions): Promise { process.exit(0); } - const green = '\x1b[32m'; - const reset = '\x1b[0m'; + const knownAttrs = { + language: standardize(Language, options.language), + framework: standardize(Framework, options.framework), + model_provider: standardize(ModelProviderEnum, options.modelProvider), + memory: standardize(Memory, options.memory ?? 'none'), + protocol: standardize(Protocol, options.protocol ?? 'http'), + build: standardize(Build, options.build ?? 'codezip'), + agent_type: standardize(AgentType, options.type ?? 'create'), + network_mode: standardize(NetworkModeEnum, options.networkMode ?? 'public'), + has_agent: options.agent !== false, + }; - // Progress callback for real-time output - const onProgress: ProgressCallback | undefined = options.json - ? undefined - : (step, status) => { - if (status === 'done') { - console.log(`${green}[done]${reset} ${step}`); - } else if (status === 'error') { - console.log(`\x1b[31m[error]${reset} ${step}`); + await runCliCommand( + 'create', + !!options.json, + async () => { + const validation = validateCreateOptions(options, cwd); + if (!validation.valid) { + throw new Error(validation.error); + } + const green = '\x1b[32m'; + const reset = '\x1b[0m'; + + // Progress callback for real-time output + const onProgress: ProgressCallback | undefined = options.json + ? undefined + : (step, status) => { + if (status === 'done') { + console.log(`${green}[done]${reset} ${step}`); + } else if (status === 'error') { + console.log(`\x1b[31m[error]${reset} ${step}`); + } + // 'start' is silent - we only show when done + }; + + // Commander.js --no-agent sets agent=false, not noAgent=true + const skipAgent = options.agent === false; + + const result = skipAgent + ? await createProject({ + name: projectName!, + cwd, + skipGit: options.skipGit, + skipInstall: options.skipInstall, + onProgress, + }) + : await createProjectWithAgent({ + name: name!, + projectName, + cwd, + type: options.type as 'create' | 'import' | undefined, + buildType: (options.build as BuildType) ?? 'CodeZip', + language: (options.language as TargetLanguage) ?? (options.type === 'import' ? 'Python' : undefined), + framework: options.framework as SDKFramework | undefined, + modelProvider: options.modelProvider as ModelProvider | undefined, + apiKey: options.apiKey, + memory: (options.memory as 'none' | 'shortTerm' | 'longAndShortTerm') ?? 'none', + protocol: options.protocol as ProtocolMode | undefined, + agentId: options.agentId, + agentAliasId: options.agentAliasId, + region: options.region, + networkMode: options.networkMode as NetworkMode | undefined, + subnets: parseCommaSeparatedList(options.subnets), + securityGroups: parseCommaSeparatedList(options.securityGroups), + idleTimeout: options.idleTimeout ? Number(options.idleTimeout) : undefined, + maxLifetime: options.maxLifetime ? Number(options.maxLifetime) : undefined, + sessionStorageMountPath: options.sessionStorageMountPath, + withConfigBundle: options.withConfigBundle, + skipGit: options.skipGit, + skipInstall: options.skipInstall, + skipPythonSetup: options.skipPythonSetup, + onProgress, + }); + + if (!result.success) { + throw result.error; + } + + if (options.json) { + console.log(JSON.stringify(result)); + } else { + printCreateSummary(projectName!, result.agentName, options.language, options.framework); + if (options.skipInstall) { + console.log( + "\nDependency installation was skipped. Run 'npm install' in agentcore/cdk/ and 'uv sync' in your agent directory manually." + ); } - // 'start' is silent - we only show when done - }; + } +<<<<<<< HEAD // Commander.js --no-agent sets agent=false, not noAgent=true const skipAgent = options.agent === false; @@ -305,6 +398,12 @@ async function handleCreateAgentCLI(options: CreateOptions): Promise { } process.exit(result.success ? 0 : 1); +======= + return knownAttrs; + }, + knownAttrs + ); +>>>>>>> origin/main } export const registerCreate = (program: Command) => { diff --git a/src/cli/commands/create/types.ts b/src/cli/commands/create/types.ts index a46a85885..72547679c 100644 --- a/src/cli/commands/create/types.ts +++ b/src/cli/commands/create/types.ts @@ -1,3 +1,4 @@ +import type { Result } from '../../../lib/result'; import type { VpcOptions } from '../shared/vpc-utils'; export interface CreateOptions extends VpcOptions { @@ -37,12 +38,9 @@ export interface CreateOptions extends VpcOptions { truncationStrategy?: string; } -export interface CreateResult { - success: boolean; +export type CreateResult = Result<{ projectPath?: string; agentName?: string; - error?: string; dryRun?: boolean; wouldCreate?: string[]; - warnings?: string[]; -} +}> & { warnings?: string[] }; diff --git a/src/cli/commands/deploy/__tests__/deploy.test.ts b/src/cli/commands/deploy/__tests__/deploy.test.ts index ea05fe684..f11fa93b7 100644 --- a/src/cli/commands/deploy/__tests__/deploy.test.ts +++ b/src/cli/commands/deploy/__tests__/deploy.test.ts @@ -1,4 +1,6 @@ import { runCLI } from '../../../../test-utils/index.js'; +import { runDeploy, runDiff } from '../actions.js'; +import { StackSelectionStrategy } from '@aws-cdk/toolkit-lib'; import { randomUUID } from 'node:crypto'; import { mkdir, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; @@ -56,3 +58,37 @@ describe('deploy without agents', () => { expect(json.error.toLowerCase()).toContain('no resources defined'); }); }); + +describe('runDiff', () => { + it('passes stack selection pattern to toolkit wrapper diff', async () => { + let captured: unknown; + const fakeWrapper = { + diff: (opts?: unknown) => { + captured = opts; + }, + }; + + await runDiff(fakeWrapper as any, 'AgentCore-myapp-prod'); + + expect(captured).toEqual({ + stacks: { strategy: StackSelectionStrategy.PATTERN_MUST_MATCH, patterns: ['AgentCore-myapp-prod'] }, + }); + }); +}); + +describe('runDeploy', () => { + it('passes stack selection pattern to toolkit wrapper deploy', async () => { + let captured: unknown; + const fakeWrapper = { + deploy: (opts?: unknown) => { + captured = opts; + }, + }; + + await runDeploy(fakeWrapper as any, 'AgentCore-myapp-prod'); + + expect(captured).toEqual({ + stacks: { strategy: StackSelectionStrategy.PATTERN_MUST_MATCH, patterns: ['AgentCore-myapp-prod'] }, + }); + }); +}); diff --git a/src/cli/commands/deploy/__tests__/utils.test.ts b/src/cli/commands/deploy/__tests__/utils.test.ts new file mode 100644 index 000000000..8bf07311a --- /dev/null +++ b/src/cli/commands/deploy/__tests__/utils.test.ts @@ -0,0 +1,54 @@ +import type { AgentCoreProjectSpec } from '../../../../schema'; +import { computeDeployAttrs } from '../utils.js'; +import { describe, expect, it } from 'vitest'; + +describe('computeDeployAttrs', () => { + it('computes counts from a populated spec', () => { + const projectSpec = { + runtimes: [{}, {}], + memories: [{}], + credentials: [{}, {}, {}], + evaluators: [{}], + onlineEvalConfigs: [{}, {}], + agentCoreGateways: [{ targets: [{}, {}] }, { targets: [{}] }], + policyEngines: [{ policies: [{}, {}] }, { policies: [{}] }], + } as unknown as Partial; + + expect(computeDeployAttrs(projectSpec, 'diff')).toEqual({ + runtime_count: 2, + memory_count: 1, + credential_count: 3, + evaluator_count: 1, + online_eval_count: 2, + gateway_count: 2, + gateway_target_count: 3, + policy_engine_count: 2, + policy_count: 3, + mode: 'diff', + }); + }); + + it('returns zeros for empty spec', () => { + expect(computeDeployAttrs({}, 'deploy')).toEqual({ + runtime_count: 0, + memory_count: 0, + credential_count: 0, + evaluator_count: 0, + online_eval_count: 0, + gateway_count: 0, + gateway_target_count: 0, + policy_engine_count: 0, + policy_count: 0, + mode: 'deploy', + }); + }); + + it('handles dry-run mode', () => { + const projectSpec = { runtimes: [{}] } as unknown as Partial; + const attrs = computeDeployAttrs(projectSpec, 'dry-run'); + + expect(attrs.runtime_count).toBe(1); + expect(attrs.memory_count).toBe(0); + expect(attrs.mode).toBe('dry-run'); + }); +}); diff --git a/src/cli/commands/deploy/actions.ts b/src/cli/commands/deploy/actions.ts index 803cc2852..dcd6754c5 100644 --- a/src/cli/commands/deploy/actions.ts +++ b/src/cli/commands/deploy/actions.ts @@ -1,9 +1,18 @@ +<<<<<<< HEAD import { ConfigIO, SecureCredentials } from '../../../lib'; import type { AgentCoreMcpSpec, DeployedState, HarnessDeployedState } from '../../../schema'; import { applyTargetRegionToEnv } from '../../aws'; import { validateAwsCredentials } from '../../aws/account'; import type { DeployMessage } from '../../cdk/toolkit-lib'; import { createSwitchableIoHost } from '../../cdk/toolkit-lib'; +======= +import { ConfigIO, ResourceNotFoundError, SecureCredentials, ValidationError, toError } from '../../../lib'; +import type { AgentCoreMcpSpec, DeployedState } from '../../../schema'; +import { applyTargetRegionToEnv } from '../../aws'; +import { validateAwsCredentials } from '../../aws/account'; +import { CdkToolkitWrapper, createSwitchableIoHost } from '../../cdk/toolkit-lib'; +import type { SwitchableIoHost } from '../../cdk/toolkit-lib'; +>>>>>>> origin/main import { buildDeployedState, getStackOutputs, @@ -35,7 +44,10 @@ import { } from '../../operations/deploy'; import { computeProjectDeployHash } from '../../operations/deploy/change-detection'; import { formatTargetStatus, getGatewayTargetStatuses } from '../../operations/deploy/gateway-status'; +<<<<<<< HEAD import { createDeploymentManager } from '../../operations/deploy/imperative'; +======= +>>>>>>> origin/main import { deleteOrphanedABTests, setupABTests } from '../../operations/deploy/post-deploy-ab-tests'; import { resolveConfigBundleComponentKeys, @@ -43,7 +55,12 @@ import { } from '../../operations/deploy/post-deploy-config-bundles'; import { setupHttpGateways } from '../../operations/deploy/post-deploy-http-gateways'; import { enableOnlineEvalConfigs } from '../../operations/deploy/post-deploy-online-evals'; +<<<<<<< HEAD +======= +import { toStackName } from '../import/import-utils'; +>>>>>>> origin/main import type { DeployResult } from './types'; +import { StackSelectionStrategy } from '@aws-cdk/toolkit-lib'; export interface ValidatedDeployOptions { target: string; @@ -59,6 +76,38 @@ export interface ValidatedDeployOptions { const AGENT_NEXT_STEPS = ['agentcore invoke', 'agentcore status']; const MEMORY_ONLY_NEXT_STEPS = ['agentcore add agent', 'agentcore status']; +export async function runDiff( + toolkitWrapper: CdkToolkitWrapper, + stackName: string, + switchableIoHost?: SwitchableIoHost +): Promise { + const diffIoHost = switchableIoHost ?? createSwitchableIoHost(); + let hasDiffContent = false; + diffIoHost.setOnRawMessage((code, _level, message) => { + if (!message) return; + // I4002: formatted diff per stack, I4001: overall diff summary + if (code === 'CDK_TOOLKIT_I4002' || code === 'CDK_TOOLKIT_I4001') { + hasDiffContent = true; + console.log(message); + } + }); + diffIoHost.setVerbose(true); + await toolkitWrapper.diff({ + stacks: { strategy: StackSelectionStrategy.PATTERN_MUST_MATCH, patterns: [stackName] }, + }); + if (!hasDiffContent) { + console.log('No stack differences detected.'); + } + diffIoHost.setVerbose(false); + diffIoHost.setOnRawMessage(null); +} + +export async function runDeploy(toolkitWrapper: CdkToolkitWrapper, stackName: string): Promise { + await toolkitWrapper.deploy({ + stacks: { strategy: StackSelectionStrategy.PATTERN_MUST_MATCH, patterns: [stackName] }, + }); +} + export async function handleDeploy(options: ValidatedDeployOptions): Promise { let toolkitWrapper = null; let restoreEnv: (() => void) | null = null; @@ -89,7 +138,7 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise { - if (!message) return; - // I4002: formatted diff per stack, I4001: overall diff summary - if (code === 'CDK_TOOLKIT_I4002' || code === 'CDK_TOOLKIT_I4001') { - hasDiffContent = true; - console.log(message); - } - }); - diffIoHost.setVerbose(true); - await toolkitWrapper.diff(); - if (!hasDiffContent) { - console.log('No stack differences detected.'); - } - diffIoHost.setVerbose(false); - diffIoHost.setOnRawMessage(null); + await runDiff(toolkitWrapper, targetStackName, switchableIoHost); endStep('success'); logger.finalize(true); @@ -349,7 +389,7 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise undefined); +>>>>>>> origin/main let deployedState = buildDeployedState({ targetName: target.name, stackName, @@ -736,8 +780,8 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise>>>>>> origin/main import { validateDeployOptions } from './validate'; import type { Command } from '@commander-js/extra-typings'; import { Text, render } from 'ink'; @@ -37,8 +44,74 @@ async function handleDeployCLI(options: DeployOptions): Promise { process.exit(1); } +<<<<<<< HEAD const progressUtil = options.progress ? createSpinnerProgress() : undefined; const onProgress = progressUtil?.onProgress; +======= + // Compute attrs upfront from project spec (available before deploy) + const mode = options.diff ? 'diff' : options.plan ? 'dry-run' : 'deploy'; + const attrs = await new ConfigIO() + .readProjectSpec() + .then(spec => computeDeployAttrs(spec, mode)) + .catch(() => ({ ...DEFAULT_DEPLOY_ATTRS, mode }) as const); + + const { deployResult } = await withCommandRunTelemetry('deploy', attrs, async () => { + const result = await executeDeploy(options).catch( + (e): DeployResult => ({ success: false, error: e instanceof Error ? e : new Error(getErrorMessage(e)) }) + ); + if (!result.success) { + return { success: false as const, error: result.error, deployResult: result }; + } + return { success: true as const, deployResult: result }; + }); + + // ALL output happens here, after telemetry + if (!deployResult.success) { + if (options.json) { + console.log(JSON.stringify(serializeResult(deployResult))); + } else { + console.error(deployResult.error.message); + if (deployResult.logPath) { + console.error(`Log: ${deployResult.logPath}`); + } + } + process.exit(1); + } + + printDeployResult(deployResult, options); + + if (deployResult.postDeployWarnings && deployResult.postDeployWarnings.length > 0) { + process.exit(2); + } + process.exit(0); +} + +async function executeDeploy(options: DeployOptions): Promise { + let spinner: NodeJS.Timeout | undefined; + + // Progress callback for --progress mode + const onProgress = options.progress + ? (step: string, status: 'start' | 'success' | 'error') => { + if (spinner) { + clearInterval(spinner); + process.stdout.write('\r\x1b[K'); // Clear line + } + + if (status === 'start') { + let i = 0; + process.stdout.write(`${SPINNER_FRAMES[0]} ${step}...`); + spinner = setInterval(() => { + i = (i + 1) % SPINNER_FRAMES.length; + process.stdout.write(`\r${SPINNER_FRAMES[i]} ${step}...`); + }, 80); + } else if (status === 'success') { + console.log(`✓ ${step}`); + } else { + console.log(`✗ ${step}`); + } + } + : undefined; +>>>>>>> origin/main const onResourceEvent = options.verbose ? (message: string) => { @@ -58,17 +131,16 @@ async function handleDeployCLI(options: DeployOptions): Promise { progressUtil?.cleanup(); + return result; +} + +function printDeployResult(result: DeployResult & { success: true }, options: DeployOptions): void { if (options.json) { console.log(JSON.stringify(result)); - } else if (result.success) { - if (options.diff) { - console.log(`\n✓ Diff complete for '${result.targetName}' (stack: ${result.stackName})`); - } else if (options.plan) { - console.log(`\n✓ Dry run complete for '${result.targetName}' (stack: ${result.stackName})`); - console.log('\nRun `agentcore deploy` to deploy.'); - } else { - console.log(`\n✓ Deployed to '${result.targetName}' (stack: ${result.stackName})`); + return; + } +<<<<<<< HEAD // Show stack outputs in non-JSON mode if (result.outputs && Object.keys(result.outputs).length > 0) { console.log('\nOutputs:'); @@ -98,15 +170,50 @@ async function handleDeployCLI(options: DeployOptions): Promise { if (result.logPath) { console.log(`\nLog: ${result.logPath}`); } +======= + if (options.diff) { + console.log(`\n✓ Diff complete for '${result.targetName}' (stack: ${result.stackName})`); + } else if (options.plan) { + console.log(`\n✓ Dry run complete for '${result.targetName}' (stack: ${result.stackName})`); + console.log('\nRun `agentcore deploy` to deploy.'); +>>>>>>> origin/main } else { - console.error(result.error); - if (result.logPath) { - console.error(`Log: ${result.logPath}`); + console.log(`\n✓ Deployed to '${result.targetName}' (stack: ${result.stackName})`); + + // Show stack outputs in non-JSON mode + if (result.outputs && Object.keys(result.outputs).length > 0) { + console.log('\nOutputs:'); + for (const [key, value] of Object.entries(result.outputs)) { + console.log(` ${key}: ${value}`); + } + } + + if (result.postDeployWarnings && result.postDeployWarnings.length > 0) { + console.log('\n⚠ Post-deploy warnings:'); + for (const warning of result.postDeployWarnings) { + console.log(` ${warning}`); + } + } + + if (result.notes && result.notes.length > 0) { + for (const note of result.notes) { + console.log(`\nNote: ${note}`); + } + } + + if (result.nextSteps && result.nextSteps.length > 0) { + console.log(`Next: ${result.nextSteps.join(' | ')}`); } } +<<<<<<< HEAD const hasPostDeployWarnings = result.success && result.postDeployWarnings && result.postDeployWarnings.length > 0; process.exit(result.success ? (hasPostDeployWarnings ? 2 : 0) : 1); +======= + if (result.logPath) { + console.log(`\nLog: ${result.logPath}`); + } +>>>>>>> origin/main } export const registerDeploy = (program: Command) => { diff --git a/src/cli/commands/deploy/types.ts b/src/cli/commands/deploy/types.ts index 44cdc7847..1c7306dad 100644 --- a/src/cli/commands/deploy/types.ts +++ b/src/cli/commands/deploy/types.ts @@ -1,3 +1,5 @@ +import type { Result } from '../../../lib/result'; + export interface DeployOptions { target?: string; yes?: boolean; @@ -8,21 +10,21 @@ export interface DeployOptions { diff?: boolean; } -export interface DeployResult { - success: boolean; +export type DeployResult = Result<{ targetName?: string; stackName?: string; outputs?: Record; - logPath?: string; nextSteps?: string[]; notes?: string[]; postDeployWarnings?: string[]; +<<<<<<< HEAD error?: string; } +======= +}> & { logPath?: string }; +>>>>>>> origin/main -export interface PreflightResult { - success: boolean; +export type PreflightResult = Result<{ stackNames?: string[]; needsBootstrap?: boolean; - error?: string; -} +}>; diff --git a/src/cli/commands/deploy/utils.ts b/src/cli/commands/deploy/utils.ts new file mode 100644 index 000000000..d0db1ec08 --- /dev/null +++ b/src/cli/commands/deploy/utils.ts @@ -0,0 +1,33 @@ +import type { AgentCoreProjectSpec } from '../../../schema'; + +export type DeployMode = 'deploy' | 'dry-run' | 'diff'; + +export const DEFAULT_DEPLOY_ATTRS = { + runtime_count: 0, + memory_count: 0, + credential_count: 0, + evaluator_count: 0, + online_eval_count: 0, + gateway_count: 0, + gateway_target_count: 0, + policy_engine_count: 0, + policy_count: 0, + mode: 'deploy' as DeployMode, +}; + +export function computeDeployAttrs(projectSpec: Partial, mode: DeployMode) { + const gateways = projectSpec.agentCoreGateways ?? []; + const policyEngines = projectSpec.policyEngines ?? []; + return { + runtime_count: (projectSpec.runtimes ?? []).length, + memory_count: (projectSpec.memories ?? []).length, + credential_count: (projectSpec.credentials ?? []).length, + evaluator_count: (projectSpec.evaluators ?? []).length, + online_eval_count: (projectSpec.onlineEvalConfigs ?? []).length, + gateway_count: gateways.length, + gateway_target_count: gateways.reduce((sum, g) => sum + (g.targets ?? []).length, 0), + policy_engine_count: policyEngines.length, + policy_count: policyEngines.reduce((sum, pe) => sum + (pe.policies ?? []).length, 0), + mode, + }; +} diff --git a/src/cli/commands/dev/browser-mode.ts b/src/cli/commands/dev/browser-mode.ts index 77342e7f1..4212c209e 100644 --- a/src/cli/commands/dev/browser-mode.ts +++ b/src/cli/commands/dev/browser-mode.ts @@ -68,24 +68,26 @@ async function resolveDeployedHandlers( result.onListMemoryRecords = async (memoryName, namespace, strategyId) => { const memory = memories.find(m => m.name === memoryName); if (!memory) return { success: false, error: `Memory "${memoryName}" not found in deployed state` }; - return listMemoryRecords({ + const res = await listMemoryRecords({ region: memory.region, memoryId: memory.memoryId, namespace, memoryStrategyId: strategyId, }); + return res.success ? res : { success: false as const, error: res.error.message }; }; result.onRetrieveMemoryRecords = async (memoryName, namespace, searchQuery, strategyId) => { const memory = memories.find(m => m.name === memoryName); if (!memory) return { success: false, error: `Memory "${memoryName}" not found in deployed state` }; - return retrieveMemoryRecords({ + const res = await retrieveMemoryRecords({ region: memory.region, memoryId: memory.memoryId, namespace, searchQuery, memoryStrategyId: strategyId, }); + return res.success ? res : { success: false as const, error: res.error.message }; }; } @@ -254,13 +256,14 @@ export async function runBrowserMode(opts: BrowserModeOptions): Promise { const context = await loadDeployedProjectConfig(configIO); const resolved = await resolveAgentOrHarness(context, { runtime: agentName, harness: harnessName }); if (!resolved.success) return { success: false, error: resolved.error }; - return listTraces({ + const res = await listTraces({ region: resolved.agent.region, runtimeId: resolved.agent.runtimeId, agentName: resolved.agent.agentName, startTime, endTime, }); + return res.success ? res : { success: false as const, error: res.error.message }; } catch (err) { return { success: false, @@ -274,7 +277,7 @@ export async function runBrowserMode(opts: BrowserModeOptions): Promise { const context = await loadDeployedProjectConfig(configIO); const resolved = await resolveAgentOrHarness(context, { runtime: agentName, harness: harnessName }); if (!resolved.success) return { success: false, error: resolved.error }; - return fetchTraceRecords({ + const res = await fetchTraceRecords({ region: resolved.agent.region, runtimeId: resolved.agent.runtimeId, traceId, @@ -282,6 +285,7 @@ export async function runBrowserMode(opts: BrowserModeOptions): Promise { endTime, includeSpans: true, }); + return res.success ? res : { success: false as const, error: res.error.message }; } catch (err) { return { success: false, diff --git a/src/cli/commands/eval/command.tsx b/src/cli/commands/eval/command.tsx index 9dff195c4..4a086584b 100644 --- a/src/cli/commands/eval/command.tsx +++ b/src/cli/commands/eval/command.tsx @@ -1,3 +1,4 @@ +import { serializeResult } from '../../../lib'; import { getErrorMessage } from '../../errors'; import { handleListEvalRuns } from '../../operations/eval'; import { getResultsPath } from '../../operations/eval/storage'; @@ -27,13 +28,13 @@ export const registerEval = (program: Command) => { }); if (cliOptions.json) { - console.log(JSON.stringify(result)); + console.log(JSON.stringify(serializeResult(result))); process.exit(result.success ? 0 : 1); return; } if (!result.success) { - render({result.error}); + render({result.error.message}); process.exit(1); } diff --git a/src/cli/commands/import/__tests__/idempotency.test.ts b/src/cli/commands/import/__tests__/idempotency.test.ts index 4b1938e28..7cf7648aa 100644 --- a/src/cli/commands/import/__tests__/idempotency.test.ts +++ b/src/cli/commands/import/__tests__/idempotency.test.ts @@ -10,6 +10,7 @@ // ── Import the function under test AFTER mocks ──────────────────────────────── import { handleImport } from '../actions'; import type { ParsedStarterToolkitConfig } from '../types'; +import assert from 'node:assert'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; // ── Hoisted mock fns (available inside vi.mock factories) ───────────────────── @@ -76,7 +77,20 @@ const { vi.mock('../../../../lib', () => ({ APP_DIR: 'app', ConfigIO: MockConfigIOClass, + NoProjectError: class NoProjectError extends Error { + constructor(msg?: string) { + super(msg ?? 'No agentcore project found'); + this.name = 'NoProjectError'; + } + }, + ValidationError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ValidationError'; + } + }, findConfigRoot: (...args: unknown[]) => mockFindConfigRoot(...args), + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), })); vi.mock('../../../aws/account', () => ({ @@ -279,7 +293,7 @@ describe('Import Idempotency (Test Group 7)', () => { const result = await handleImport({ source: '/tmp/config.yaml' }); - expect(result.success).toBe(true); + assert(result.success); expect(result.importedAgents).toContain('my-agent'); expect(result.importedMemories).toContain('my-memory'); @@ -393,7 +407,7 @@ describe('Import Idempotency (Test Group 7)', () => { const result = await handleImport({ source: '/tmp/config.yaml' }); - expect(result.success).toBe(true); + assert(result.success); expect(result.importedAgents).toEqual([]); expect(result.importedMemories).toEqual([]); }); @@ -610,8 +624,8 @@ describe('Import Idempotency (Test Group 7)', () => { const result = await handleImport({ source: '/tmp/config.yaml' }); - expect(result.success).toBe(false); - expect(result.error).toContain('No agents found'); + assert(!result.success); + expect(result.error.message).toContain('No agents found'); }); it('returns error when no project found', async () => { @@ -619,8 +633,8 @@ describe('Import Idempotency (Test Group 7)', () => { const result = await handleImport({ source: '/tmp/config.yaml' }); - expect(result.success).toBe(false); - expect(result.error).toContain('No agentcore project found'); + assert(!result.success); + expect(result.error.message).toContain('No agentcore project found'); }); }); diff --git a/src/cli/commands/import/__tests__/import-evaluator.test.ts b/src/cli/commands/import/__tests__/import-evaluator.test.ts index 5e6fb5e96..1795ce4fe 100644 --- a/src/cli/commands/import/__tests__/import-evaluator.test.ts +++ b/src/cli/commands/import/__tests__/import-evaluator.test.ts @@ -210,6 +210,49 @@ describe('toEvaluatorSpec', () => { expect(result.tags).toBeUndefined(); }); + + it('forwards kmsKeyArn when present', () => { + const detail: GetEvaluatorResult = { + evaluatorId: 'eval-kms', + evaluatorArn: 'arn:aws:bedrock-agentcore:us-west-2:123456789012:evaluator/eval-kms', + evaluatorName: 'kms_eval', + level: 'SESSION', + status: 'ACTIVE', + evaluatorConfig: { + llmAsAJudge: { + model: 'anthropic.claude-3-5-sonnet-20241022-v2:0', + instructions: 'Evaluate', + ratingScale: { numerical: [{ value: 1, label: 'Low', definition: 'Low' }] }, + }, + }, + kmsKeyArn: 'arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012', + }; + + const result = toEvaluatorSpec(detail, 'kms_eval'); + + expect(result.kmsKeyArn).toBe('arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012'); + }); + + it('omits kmsKeyArn when not present', () => { + const detail: GetEvaluatorResult = { + evaluatorId: 'eval-no-kms', + evaluatorArn: 'arn:aws:bedrock-agentcore:us-west-2:123456789012:evaluator/eval-no-kms', + evaluatorName: 'no_kms_eval', + level: 'SESSION', + status: 'ACTIVE', + evaluatorConfig: { + llmAsAJudge: { + model: 'anthropic.claude-3-5-sonnet-20241022-v2:0', + instructions: 'Evaluate', + ratingScale: { numerical: [{ value: 1, label: 'Low', definition: 'Low' }] }, + }, + }, + }; + + const result = toEvaluatorSpec(detail, 'no_kms_eval'); + + expect(result.kmsKeyArn).toBeUndefined(); + }); }); // ============================================================================ diff --git a/src/cli/commands/import/__tests__/import-gateway-flow.test.ts b/src/cli/commands/import/__tests__/import-gateway-flow.test.ts index 183a6e63f..34803e8eb 100644 --- a/src/cli/commands/import/__tests__/import-gateway-flow.test.ts +++ b/src/cli/commands/import/__tests__/import-gateway-flow.test.ts @@ -11,6 +11,7 @@ * - Non-READY gateway warning */ import { handleImportGateway } from '../import-gateway'; +import assert from 'node:assert'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; // ── Hoisted mock fns ──────────────────────────────────────────────────────── @@ -56,6 +57,12 @@ const { vi.mock('../../../../lib', () => ({ APP_DIR: 'app', ConfigIO: MockConfigIOClass, + NoProjectError: class NoProjectError extends Error { + constructor(msg?: string) { + super(msg ?? 'No agentcore project found'); + this.name = 'NoProjectError'; + } + }, findConfigRoot: (...args: unknown[]) => mockFindConfigRoot(...args), })); @@ -177,7 +184,7 @@ describe('handleImportGateway', () => { it('successfully imports a gateway with --arn', async () => { const result = await handleImportGateway({ arn: GATEWAY_ARN }); - expect(result.success).toBe(true); + assert(result.success); expect(result.resourceId).toBe(GATEWAY_ID); expect(result.resourceType).toBe('gateway'); expect(result.resourceName).toBe(GATEWAY_NAME); @@ -199,8 +206,8 @@ describe('handleImportGateway', () => { const result = await handleImportGateway({ arn: GATEWAY_ARN }); - expect(result.success).toBe(false); - expect(result.error).toBe('Phase 2 failed'); + assert(!result.success); + expect(result.error.message).toBe('Phase 2 failed'); // First call = write merged config, second call = rollback expect(mockConfigIOInstance.writeProjectSpec).toHaveBeenCalledTimes(2); @@ -213,8 +220,8 @@ describe('handleImportGateway', () => { const result = await handleImportGateway({ arn: GATEWAY_ARN }); - expect(result.success).toBe(false); - expect(result.error).toContain('Could not find logical ID'); + assert(!result.success); + expect(result.error.message).toContain('Could not find logical ID'); // First call = write merged config, second call = rollback expect(mockConfigIOInstance.writeProjectSpec).toHaveBeenCalledTimes(2); @@ -231,8 +238,8 @@ describe('handleImportGateway', () => { const result = await handleImportGateway({ arn: GATEWAY_ARN }); - expect(result.success).toBe(false); - expect(result.error).toContain('already exists'); + assert(!result.success); + expect(result.error.message).toContain('already exists'); expect(mockConfigIOInstance.writeProjectSpec).not.toHaveBeenCalled(); }); @@ -254,7 +261,7 @@ describe('handleImportGateway', () => { const result = await handleImportGateway({ arn: GATEWAY_ARN }); - expect(result.success).toBe(true); + assert(result.success); expect(result.resourceName).toBe('ExistingGateway'); expect(mockConfigIOInstance.writeProjectSpec).toHaveBeenCalledTimes(1); expect(mockExecuteCdkImportPipeline).toHaveBeenCalled(); @@ -278,7 +285,7 @@ describe('handleImportGateway', () => { const result = await handleImportGateway({ arn: GATEWAY_ARN, name: 'myCustomName' }); - expect(result.success).toBe(true); + assert(result.success); expect(result.resourceName).toBe('myCustomName'); expect(mockConfigIOInstance.writeProjectSpec).toHaveBeenCalledTimes(1); expect(mockExecuteCdkImportPipeline).toHaveBeenCalled(); @@ -302,8 +309,8 @@ describe('handleImportGateway', () => { const result = await handleImportGateway({ arn: GATEWAY_ARN }); - expect(result.success).toBe(false); - expect(result.error).toContain('already exists'); + assert(!result.success); + expect(result.error.message).toContain('already exists'); }); }); @@ -315,15 +322,15 @@ describe('handleImportGateway', () => { const result = await handleImportGateway({ arn: GATEWAY_ARN }); - expect(result.success).toBe(false); - expect(result.error).toContain('Invalid name'); + assert(!result.success); + expect(result.error.message).toContain('Invalid name'); expect(mockConfigIOInstance.writeProjectSpec).not.toHaveBeenCalled(); }); it('uses --name override with original resourceName preserved', async () => { const result = await handleImportGateway({ arn: GATEWAY_ARN, name: 'myCustomName' }); - expect(result.success).toBe(true); + assert(result.success); expect(result.resourceName).toBe('myCustomName'); const writtenSpec = mockConfigIOInstance.writeProjectSpec.mock.calls[0]![0]; @@ -343,7 +350,7 @@ describe('handleImportGateway', () => { const result = await handleImportGateway({}); - expect(result.success).toBe(true); + assert(result.success); expect(result.resourceId).toBe(GATEWAY_ID); expect(mockGetGatewayDetail).toHaveBeenCalledWith({ region: REGION, gatewayId: GATEWAY_ID }); }); @@ -356,8 +363,8 @@ describe('handleImportGateway', () => { const result = await handleImportGateway({}); - expect(result.success).toBe(false); - expect(result.error).toContain('Multiple gateways found'); + assert(!result.success); + expect(result.error.message).toContain('Multiple gateways found'); }); it('fails when no gateways exist and no --arn', async () => { @@ -365,8 +372,8 @@ describe('handleImportGateway', () => { const result = await handleImportGateway({}); - expect(result.success).toBe(false); - expect(result.error).toContain('No gateways found'); + assert(!result.success); + expect(result.error.message).toContain('No gateways found'); }); }); @@ -399,7 +406,7 @@ describe('handleImportGateway', () => { onProgress: (msg: string) => progressMessages.push(msg), }); - expect(result.success).toBe(true); + assert(result.success); // Verify warning about unmapped targets expect(progressMessages.some(m => m.includes('1 target(s) could not be mapped'))).toBe(true); @@ -414,7 +421,7 @@ describe('handleImportGateway', () => { onProgress: (msg: string) => progressMessages.push(msg), }); - expect(result.success).toBe(true); + assert(result.success); expect(progressMessages.some(m => m.includes('CREATING') && m.includes('not READY'))).toBe(true); }); }); diff --git a/src/cli/commands/import/__tests__/import-no-deploy.test.ts b/src/cli/commands/import/__tests__/import-no-deploy.test.ts index f0b84303f..001c551be 100644 --- a/src/cli/commands/import/__tests__/import-no-deploy.test.ts +++ b/src/cli/commands/import/__tests__/import-no-deploy.test.ts @@ -5,6 +5,7 @@ * that were created but never deployed (no agent_id/memory_id in YAML). */ import { parseStarterToolkitYaml } from '../yaml-parser.js'; +import assert from 'node:assert'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; @@ -30,6 +31,12 @@ vi.mock('../../../../lib', () => ({ readDeployedState = mockReadDeployedState; writeDeployedState = mockWriteDeployedState; }, + NoProjectError: class NoProjectError extends Error { + constructor(msg?: string) { + super(msg ?? 'No agentcore project found'); + this.name = 'NoProjectError'; + } + }, findConfigRoot: (...args: unknown[]) => mockFindConfigRoot(...args), })); @@ -488,7 +495,7 @@ agents: onProgress: (msg: string) => progressMessages.push(msg), }); - expect(result.success).toBe(true); + assert(result.success); expect(result.importedAgents).toEqual([]); expect(result.importedMemories).toEqual([]); expect(result.stackName).toBeDefined(); @@ -645,6 +652,7 @@ agents: const { handleImport } = await import('../actions.js'); const result = await handleImport({ source: yamlPath }); + assert(result.success); expect(result.stackName).toBe('AgentCore-myproject-default'); }); }); @@ -724,7 +732,7 @@ agents: // No physical IDs means target resolution is skipped entirely. // The import succeeds -- config merge + source copy still happen. - expect(result.success).toBe(true); + assert(result.success); expect(result.importedAgents).toEqual([]); expect(result.importedMemories).toEqual([]); }); @@ -768,7 +776,7 @@ agents: const { handleImport } = await import('../actions.js'); const result = await handleImport({ source: yamlPath }); - expect(result.success).toBe(true); + assert(result.success); expect(result.importedAgents).toEqual([]); expect(result.importedMemories).toEqual([]); }); @@ -812,7 +820,7 @@ agents: const { handleImport } = await import('../actions.js'); const result = await handleImport({ source: yamlPath }); - expect(result.success).toBe(true); + assert(result.success); // No physical IDs means target is not written to disk expect(mockWriteAWSDeploymentTargets).not.toHaveBeenCalled(); // But the stackName should still be computed using 'default' fallback diff --git a/src/cli/commands/import/__tests__/import-runtime-handler.test.ts b/src/cli/commands/import/__tests__/import-runtime-handler.test.ts index b4fb7de90..d8926feb9 100644 --- a/src/cli/commands/import/__tests__/import-runtime-handler.test.ts +++ b/src/cli/commands/import/__tests__/import-runtime-handler.test.ts @@ -10,6 +10,7 @@ * - Fails when runtime name already exists in project */ import { handleImportRuntime } from '../import-runtime'; +import assert from 'node:assert'; import { afterEach, describe, expect, it, vi } from 'vitest'; // ── Mock dependencies ──────────────────────────────────────────────────────── @@ -25,7 +26,7 @@ const mockParseAndValidateArn = vi.fn(); const mockFindResourceInDeployedState = vi.fn(); const mockFailResult = vi.fn((...args: unknown[]) => ({ success: false, - error: args[1] as string, + error: new Error(args[1] as string), resourceType: args[2] as string, resourceName: args[3] as string, logPath: 'test.log', @@ -205,8 +206,9 @@ describe('handleImportRuntime', () => { }); expect(result.success).toBe(false); - expect(result.error).toContain('Could not determine entrypoint'); - expect(result.error).toContain('--entrypoint'); + assert(!result.success); + expect(result.error.message).toContain('Could not determine entrypoint'); + expect(result.error.message).toContain('--entrypoint'); }); it('fails with clear error when entryPoint is undefined', async () => { @@ -231,7 +233,8 @@ describe('handleImportRuntime', () => { }); expect(result.success).toBe(false); - expect(result.error).toContain('Could not determine entrypoint'); + assert(!result.success); + expect(result.error.message).toContain('Could not determine entrypoint'); }); it('fails with clear error when entryPoint is empty array', async () => { @@ -256,7 +259,8 @@ describe('handleImportRuntime', () => { }); expect(result.success).toBe(false); - expect(result.error).toContain('Could not determine entrypoint'); + assert(!result.success); + expect(result.error.message).toContain('Could not determine entrypoint'); }); it('uses --entrypoint flag when provided, bypassing auto-detection', async () => { @@ -369,7 +373,8 @@ describe('handleImportRuntime', () => { }); expect(result.success).toBe(false); - expect(result.error).toContain('Multiple runtimes found'); + assert(!result.success); + expect(result.error.message).toContain('Multiple runtimes found'); }); it('errors when no runtimes exist', async () => { @@ -382,7 +387,8 @@ describe('handleImportRuntime', () => { }); expect(result.success).toBe(false); - expect(result.error).toContain('No runtimes found'); + assert(!result.success); + expect(result.error.message).toContain('No runtimes found'); }); }); @@ -546,7 +552,8 @@ describe('handleImportRuntime', () => { }); expect(result.success).toBe(false); - expect(result.error).toContain('--code'); + assert(!result.success); + expect(result.error.message).toContain('--code'); }); it('fails when source path does not exist', async () => { @@ -574,7 +581,8 @@ describe('handleImportRuntime', () => { }); expect(result.success).toBe(false); - expect(result.error).toContain('does not exist'); + assert(!result.success); + expect(result.error.message).toContain('does not exist'); }); it('fails when runtime name already exists in project', async () => { @@ -606,7 +614,8 @@ describe('handleImportRuntime', () => { }); expect(result.success).toBe(false); - expect(result.error).toContain('already exists'); + assert(!result.success); + expect(result.error.message).toContain('already exists'); }); }); }); diff --git a/src/cli/commands/import/__tests__/jwt-authorizer.test.ts b/src/cli/commands/import/__tests__/jwt-authorizer.test.ts index 024c592df..1da1bb33a 100644 --- a/src/cli/commands/import/__tests__/jwt-authorizer.test.ts +++ b/src/cli/commands/import/__tests__/jwt-authorizer.test.ts @@ -27,7 +27,20 @@ vi.mock('../../../../lib', () => ({ readDeployedState = mockReadDeployedState; writeDeployedState = mockWriteDeployedState; }, + NoProjectError: class NoProjectError extends Error { + constructor(msg?: string) { + super(msg ?? 'No agentcore project found'); + this.name = 'NoProjectError'; + } + }, + ValidationError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ValidationError'; + } + }, findConfigRoot: (...args: unknown[]) => mockFindConfigRoot(...args), + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), })); vi.mock('../../../aws/account', () => ({ diff --git a/src/cli/commands/import/__tests__/phase-failure-rollback.test.ts b/src/cli/commands/import/__tests__/phase-failure-rollback.test.ts index 149d65faf..f18f3cd71 100644 --- a/src/cli/commands/import/__tests__/phase-failure-rollback.test.ts +++ b/src/cli/commands/import/__tests__/phase-failure-rollback.test.ts @@ -7,6 +7,7 @@ */ import { handleImport } from '../actions'; import type { ParsedStarterToolkitConfig } from '../types'; +import assert from 'node:assert'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; // ── Hoisted mock fns ──────────────────────────────────────────────────────── @@ -73,7 +74,20 @@ const { vi.mock('../../../../lib', () => ({ APP_DIR: 'app', ConfigIO: MockConfigIOClass, + NoProjectError: class NoProjectError extends Error { + constructor(msg?: string) { + super(msg ?? 'No agentcore project found'); + this.name = 'NoProjectError'; + } + }, + ValidationError: class ValidationError extends Error { + constructor(msg: string) { + super(msg); + this.name = 'ValidationError'; + } + }, findConfigRoot: (...args: unknown[]) => mockFindConfigRoot(...args), + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), })); vi.mock('../../../aws/account', () => ({ @@ -249,8 +263,8 @@ describe('Config Rollback on Import Failure', () => { const result = await handleImport({ source: '/tmp/config.yaml' }); - expect(result.success).toBe(false); - expect(result.error).toContain('Phase 1 failed'); + assert(!result.success); + expect(result.error.message).toContain('Phase 1 failed'); // First call = merge write, second call = rollback with original (empty) runtimes expect(mockConfigIOInstance.writeProjectSpec).toHaveBeenCalledTimes(2); @@ -265,8 +279,8 @@ describe('Config Rollback on Import Failure', () => { const result = await handleImport({ source: '/tmp/config.yaml' }); - expect(result.success).toBe(false); - expect(result.error).toContain('Phase 2 failed'); + assert(!result.success); + expect(result.error.message).toContain('Phase 2 failed'); expect(mockConfigIOInstance.writeProjectSpec).toHaveBeenCalledTimes(2); const rollbackData = mockConfigIOInstance.writeProjectSpec.mock.calls[1]![0]; @@ -280,8 +294,8 @@ describe('Config Rollback on Import Failure', () => { const result = await handleImport({ source: '/tmp/config.yaml' }); - expect(result.success).toBe(false); - expect(result.error).toContain('CDK build failed'); + assert(!result.success); + expect(result.error.message).toContain('CDK build failed'); expect(mockConfigIOInstance.writeProjectSpec).toHaveBeenCalledTimes(2); const rollbackData = mockConfigIOInstance.writeProjectSpec.mock.calls[1]![0]; @@ -294,7 +308,7 @@ describe('Config Rollback on Import Failure', () => { const result = await handleImport({ source: '/tmp/config.yaml' }); - expect(result.success).toBe(true); + assert(result.success); // Only one write: the merge write expect(mockConfigIOInstance.writeProjectSpec).toHaveBeenCalledTimes(1); }); @@ -311,8 +325,8 @@ describe('Config Rollback on Import Failure', () => { const result = await handleImport({ source: '/tmp/config.yaml' }); - expect(result.success).toBe(false); - expect(result.error).toContain('No agents found'); + assert(!result.success); + expect(result.error.message).toContain('No agents found'); // Config was never written, so no rollback expect(mockConfigIOInstance.writeProjectSpec).not.toHaveBeenCalled(); }); @@ -338,8 +352,8 @@ describe('Config Rollback on Import Failure', () => { const result = await handleImport({ source: '/tmp/config.yaml' }); - expect(result.success).toBe(false); - expect(result.error).toContain('Could not read deployed template'); + assert(!result.success); + expect(result.error.message).toContain('Could not read deployed template'); expect(mockConfigIOInstance.writeProjectSpec).toHaveBeenCalledTimes(2); const rollbackData = mockConfigIOInstance.writeProjectSpec.mock.calls[1]![0]; diff --git a/src/cli/commands/import/actions.ts b/src/cli/commands/import/actions.ts index 71eb70f83..94323e30c 100644 --- a/src/cli/commands/import/actions.ts +++ b/src/cli/commands/import/actions.ts @@ -1,4 +1,4 @@ -import { APP_DIR, ConfigIO, findConfigRoot } from '../../../lib'; +import { APP_DIR, ConfigIO, NoProjectError, ValidationError, findConfigRoot, toError } from '../../../lib'; import type { AgentCoreProjectSpec, AgentCoreRegion, @@ -124,9 +124,10 @@ export async function handleImport(options: ImportOptions): Promise` first, then run import from inside the project.'; - logger.endStep('error', error); + const error = new NoProjectError( + 'No agentcore project found in the current directory.\nRun `agentcore create ` first, then run import from inside the project.' + ); + logger.endStep('error', error.message); logger.finalize(false); return { success: false, @@ -160,7 +161,7 @@ export async function handleImport(options: ImportOptions): Promise { console.log(`Log: ${result.logPath}`); } } else { - console.error(`\n\x1b[31m[error]${reset} Import failed: ${result.error}`); + console.error(`\n\x1b[31m[error]${reset} Import failed: ${result.error.message}`); if (result.logPath) { console.error(`Log: ${result.logPath}`); } diff --git a/src/cli/commands/import/import-evaluator.ts b/src/cli/commands/import/import-evaluator.ts index 7c6c8b0d2..914a50dfa 100644 --- a/src/cli/commands/import/import-evaluator.ts +++ b/src/cli/commands/import/import-evaluator.ts @@ -50,6 +50,7 @@ export function toEvaluatorSpec(detail: GetEvaluatorResult, localName: string): level, ...(detail.description && { description: detail.description }), config, + ...(detail.kmsKeyArn && { kmsKeyArn: detail.kmsKeyArn }), ...(detail.tags && Object.keys(detail.tags).length > 0 && { tags: detail.tags }), }; } @@ -151,7 +152,7 @@ export function registerImportEvaluator(importCmd: Command): void { console.log(` ID: ${result.resourceId}`); console.log(''); } else { - console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error}`); + console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error.message}`); if (result.logPath) { console.error(`Log: ${result.logPath}`); } diff --git a/src/cli/commands/import/import-gateway.ts b/src/cli/commands/import/import-gateway.ts index 3c2384e03..5a7822210 100644 --- a/src/cli/commands/import/import-gateway.ts +++ b/src/cli/commands/import/import-gateway.ts @@ -1,3 +1,4 @@ +import { toError } from '../../../lib'; import type { AgentCoreGateway, AgentCoreGatewayTarget, @@ -612,7 +613,7 @@ export async function handleImportGateway(options: ImportResourceOptions): Promi logger.finalize(false); return { success: false, - error: pipelineResult.error, + error: new Error(pipelineResult.error ?? 'Pipeline failed'), resourceType: 'gateway', resourceName: localName, logPath: logger.getRelativeLogPath(), @@ -638,7 +639,7 @@ export async function handleImportGateway(options: ImportResourceOptions): Promi } return { success: false, - error: message, + error: toError(err), resourceType: 'gateway', resourceName: options.name ?? '', logPath: importCtx?.logger.getRelativeLogPath(), @@ -680,7 +681,7 @@ export function registerImportGateway(importCmd: Command): void { console.log(` agentcore fetch access ${ANSI.dim}Get gateway URL and token${ANSI.reset}`); console.log(''); } else { - console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error}`); + console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error.message}`); if (result.logPath) { console.error(`Log: ${result.logPath}`); } diff --git a/src/cli/commands/import/import-memory.ts b/src/cli/commands/import/import-memory.ts index 2362d1353..02213c9f1 100644 --- a/src/cli/commands/import/import-memory.ts +++ b/src/cli/commands/import/import-memory.ts @@ -121,7 +121,7 @@ export function registerImportMemory(importCmd: Command): void { console.log(` ID: ${result.resourceId}`); console.log(''); } else { - console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error}`); + console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error.message}`); if (result.logPath) { console.error(`Log: ${result.logPath}`); } diff --git a/src/cli/commands/import/import-online-eval.ts b/src/cli/commands/import/import-online-eval.ts index 99935ecdf..b43fa174a 100644 --- a/src/cli/commands/import/import-online-eval.ts +++ b/src/cli/commands/import/import-online-eval.ts @@ -209,7 +209,7 @@ export function registerImportOnlineEval(importCmd: Command): void { console.log(` ID: ${result.resourceId}`); console.log(''); } else { - console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error}`); + console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error.message}`); if (result.logPath) { console.error(`Log: ${result.logPath}`); } diff --git a/src/cli/commands/import/import-runtime.ts b/src/cli/commands/import/import-runtime.ts index b1921d546..ff6ba0640 100644 --- a/src/cli/commands/import/import-runtime.ts +++ b/src/cli/commands/import/import-runtime.ts @@ -225,7 +225,7 @@ export function registerImportRuntime(importCmd: Command): void { console.log(` agentcore invoke ${ANSI.dim}Test your agent${ANSI.reset}`); console.log(''); } else { - console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error}`); + console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error.message}`); if (result.logPath) { console.error(`Log: ${result.logPath}`); } diff --git a/src/cli/commands/import/import-utils.ts b/src/cli/commands/import/import-utils.ts index 5829bbc6c..e29ada5ee 100644 --- a/src/cli/commands/import/import-utils.ts +++ b/src/cli/commands/import/import-utils.ts @@ -67,7 +67,7 @@ export function failResult( logger.finalize(false); return { success: false, - error, + error: new Error(error), resourceType, resourceName, logPath: logger.getRelativeLogPath(), diff --git a/src/cli/commands/import/resource-import.ts b/src/cli/commands/import/resource-import.ts index a21a160ec..ad543fd48 100644 --- a/src/cli/commands/import/resource-import.ts +++ b/src/cli/commands/import/resource-import.ts @@ -1,3 +1,4 @@ +import { toError } from '../../../lib'; import type { AgentCoreProjectSpec } from '../../../schema'; import { NAME_REGEX } from './constants'; import { executeCdkImportPipeline } from './import-pipeline'; @@ -228,7 +229,7 @@ export async function executeResourceImport( logger.finalize(false); return { success: false, - error: pipelineResult.error, + error: new Error(pipelineResult.error ?? 'Pipeline failed'), resourceType: descriptor.resourceType, resourceName: localName, logPath: logger.getRelativeLogPath(), @@ -254,7 +255,7 @@ export async function executeResourceImport( } return { success: false, - error: message, + error: toError(err), resourceType: descriptor.resourceType, resourceName: options.name ?? '', logPath: importCtx?.logger.getRelativeLogPath(), diff --git a/src/cli/commands/import/types.ts b/src/cli/commands/import/types.ts index 5d99c79c1..032f92793 100644 --- a/src/cli/commands/import/types.ts +++ b/src/cli/commands/import/types.ts @@ -1,3 +1,4 @@ +import type { Result } from '../../../lib/result'; import type { AgentCoreProjectSpec, AuthorizerConfig, @@ -89,27 +90,19 @@ export interface ResourceToImport { /** * Result of the import command. */ -export interface ImportResult { - success: boolean; - error?: string; +export type ImportResult = Result<{ projectSpec?: AgentCoreProjectSpec; importedAgents?: string[]; importedMemories?: string[]; stackName?: string; - logPath?: string; -} +}> & { logPath?: string }; /** * Result for single-resource import (runtime, memory, evaluator, etc.). */ -export interface ImportResourceResult { - success: boolean; - error?: string; - resourceType: ImportableResourceType; - resourceName: string; +export type ImportResourceResult = Result<{ resourceId?: string; - logPath?: string; -} +}> & { resourceType: ImportableResourceType; resourceName: string; logPath?: string }; /** * Options shared across import subcommands. diff --git a/src/cli/commands/invoke/action.ts b/src/cli/commands/invoke/action.ts index 4c5501b6f..0fbb4e6b8 100644 --- a/src/cli/commands/invoke/action.ts +++ b/src/cli/commands/invoke/action.ts @@ -1,4 +1,4 @@ -import { ConfigIO } from '../../../lib'; +import { ConfigIO, ResourceNotFoundError, ValidationError } from '../../../lib'; import type { AgentCoreProjectSpec, AwsDeploymentTargets, DeployedState } from '../../../schema'; import { buildAguiRunInput, @@ -55,20 +55,31 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption // Resolve target const targetNames = Object.keys(deployedState.targets); if (targetNames.length === 0) { - return { success: false, error: 'No deployed targets found. Run `agentcore deploy` first.' }; + return { + success: false, + error: new ResourceNotFoundError('No deployed targets found. Run `agentcore deploy` first.'), + }; } const selectedTargetName = options.targetName ?? targetNames[0]!; if (options.targetName && !targetNames.includes(options.targetName)) { - return { success: false, error: `Target '${options.targetName}' not found. Available: ${targetNames.join(', ')}` }; + return { + success: false, + error: new ResourceNotFoundError( + `Target '${options.targetName}' not found. Available: ${targetNames.join(', ')}` + ), + }; } const targetState = deployedState.targets[selectedTargetName]; const targetConfig = awsTargets.find(t => t.name === selectedTargetName); if (!targetConfig) { - return { success: false, error: `Target config '${selectedTargetName}' not found in aws-targets` }; + return { + success: false, + error: new ResourceNotFoundError(`Target config '${selectedTargetName}' not found in aws-targets`), + }; } // ── Route to harness or runtime ───────────────────────────────────────── @@ -94,24 +105,34 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption // ── Runtime invoke path ──────────────────────────────────────────────── if (project.runtimes.length === 0) { +<<<<<<< HEAD return { success: false, error: 'No runtimes or harnesses defined in configuration' }; +======= + return { success: false, error: new ValidationError('No agents defined in configuration') }; +>>>>>>> origin/main } // Resolve agent const agentNames = project.runtimes.map(a => a.name); if (!options.agentName && project.runtimes.length > 1) { - return { success: false, error: `Multiple runtimes found. Use --runtime to specify one: ${agentNames.join(', ')}` }; + return { + success: false, + error: new ValidationError(`Multiple runtimes found. Use --runtime to specify one: ${agentNames.join(', ')}`), + }; } const agentSpec = options.agentName ? project.runtimes.find(a => a.name === options.agentName) : project.runtimes[0]; if (options.agentName && !agentSpec) { - return { success: false, error: `Agent '${options.agentName}' not found. Available: ${agentNames.join(', ')}` }; + return { + success: false, + error: new ResourceNotFoundError(`Agent '${options.agentName}' not found. Available: ${agentNames.join(', ')}`), + }; } if (!agentSpec) { - return { success: false, error: 'No agents defined in configuration' }; + return { success: false, error: new ValidationError('No agents defined in configuration') }; } // Warn about VPC mode endpoint requirements @@ -125,7 +146,24 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption const agentState = targetState?.resources?.runtimes?.[agentSpec.name]; if (!agentState) { - return { success: false, error: `Agent '${agentSpec.name}' is not deployed to target '${selectedTargetName}'` }; + return { + success: false, + error: new ValidationError(`Agent '${agentSpec.name}' is not deployed to target '${selectedTargetName}'`), + }; + } + + // Build config bundle baggage if a bundle is associated with this agent + const deployedBundles = targetState?.resources?.configBundles ?? {}; + let baggage: string | undefined; + const bundleSpec = project.configBundles?.find(b => { + const keys = Object.keys(b.components ?? {}); + return keys.some(k => k === `{{runtime:${agentSpec.name}}}`); + }); + if (bundleSpec) { + const bundleState = deployedBundles[bundleSpec.name]; + if (bundleState?.bundleArn && bundleState?.versionId) { + baggage = `aws.agentcore.configbundle_arn=${encodeURIComponent(bundleState.bundleArn)},aws.agentcore.configbundle_version=${encodeURIComponent(bundleState.versionId)}`; + } } // Build config bundle baggage if a bundle is associated with this agent @@ -152,13 +190,18 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption } catch (err) { return { success: false, - error: `CUSTOM_JWT agent requires a bearer token. Auto-fetch failed: ${err instanceof Error ? err.message : String(err)}\nProvide one manually with --bearer-token.`, + error: new ValidationError( + `CUSTOM_JWT agent requires a bearer token. Auto-fetch failed: ${err instanceof Error ? err.message : String(err)}\nProvide one manually with --bearer-token.`, + { cause: err } + ), }; } } else { return { success: false, - error: `Agent '${agentSpec.name}' is configured for CUSTOM_JWT but no bearer token is available.\nEither provide --bearer-token or re-add the agent with --client-id and --client-secret to enable auto-fetch.`, + error: new ValidationError( + `Agent '${agentSpec.name}' is configured for CUSTOM_JWT but no bearer token is available.\nEither provide --bearer-token or re-add the agent with --client-id and --client-secret to enable auto-fetch.` + ), }; } } @@ -181,7 +224,7 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption }); const command = options.prompt; if (!command) { - return { success: false, error: '--exec requires a command (prompt)' }; + return { success: false, error: new ValidationError('--exec requires a command (prompt)') }; } logger.logPrompt(command, options.sessionId, options.userId); @@ -229,8 +272,18 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption logger.logResponse(stdout || stderr || `exit code: ${exitCode}`); if (options.json) { + if (exitCode === 0) { + return { + success: true, + agentName: agentSpec.name, + targetName: selectedTargetName, + response: JSON.stringify({ stdout, stderr, exitCode, status }), + logFilePath: logger.logFilePath, + }; + } return { - success: exitCode === 0, + success: false, + error: new Error(`Command exited with code ${exitCode}`), agentName: agentSpec.name, targetName: selectedTargetName, response: JSON.stringify({ stdout, stderr, exitCode, status }), @@ -241,9 +294,9 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption if (exitCode === undefined) { return { success: false, + error: new Error('Command stream ended without exit code'), agentName: agentSpec.name, targetName: selectedTargetName, - error: 'Command stream ended without exit code', logFilePath: logger.logFilePath, }; } @@ -251,9 +304,10 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption if (exitCode !== 0) { return { success: false, + error: new Error(`Command exited with code ${exitCode}${status === 'TIMED_OUT' ? ' (timed out)' : ''}`), agentName: agentSpec.name, targetName: selectedTargetName, - error: `Command exited with code ${exitCode}${status === 'TIMED_OUT' ? ' (timed out)' : ''}`, + response: JSON.stringify({ stdout, stderr, exitCode, status }), logFilePath: logger.logFilePath, }; } @@ -295,7 +349,9 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption } catch (err) { return { success: false, - error: `Failed to list MCP tools: ${err instanceof Error ? err.message : String(err)}`, + error: new Error(`Failed to list MCP tools: ${err instanceof Error ? err.message : String(err)}`, { + cause: err, + }), }; } } @@ -305,7 +361,7 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption if (!options.tool) { return { success: false, - error: 'MCP call-tool requires --tool . Use "list-tools" to see available tools.', + error: new Error('MCP call-tool requires --tool . Use "list-tools" to see available tools.'), }; } let args: Record = {}; @@ -313,7 +369,7 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption try { args = JSON.parse(options.input) as Record; } catch { - return { success: false, error: `Invalid JSON for --input: ${options.input}` }; + return { success: false, error: new ValidationError(`Invalid JSON for --input: ${options.input}`) }; } } try { @@ -329,7 +385,9 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption } catch (err) { return { success: false, - error: `Failed to call MCP tool: ${err instanceof Error ? err.message : String(err)}`, + error: new Error(`Failed to call MCP tool: ${err instanceof Error ? err.message : String(err)}`, { + cause: err, + }), }; } } @@ -337,14 +395,15 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption if (!options.prompt) { return { success: false, - error: - 'MCP agents require a command. Usage:\n agentcore invoke list-tools\n agentcore invoke call-tool --tool --input \'{"arg": "value"}\'', + error: new ValidationError( + 'MCP agents require a command. Usage:\n agentcore invoke list-tools\n agentcore invoke call-tool --tool --input \'{"arg": "value"}\'' + ), }; } } if (!options.prompt) { - return { success: false, error: 'No prompt provided. Usage: agentcore invoke "your prompt"' }; + return { success: false, error: new ValidationError('No prompt provided. Usage: agentcore invoke "your prompt"') }; } // A2A protocol handling — send JSON-RPC message/send via InvokeAgentRuntime @@ -377,7 +436,10 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption response, }; } catch (err) { - return { success: false, error: `A2A invoke failed: ${err instanceof Error ? err.message : String(err)}` }; + return { + success: false, + error: new Error(`A2A invoke failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err }), + }; } } @@ -422,8 +484,20 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption logger.logResponse(response); + if (hasError) { + return { + success: false, + error: new Error(response), + agentName: agentSpec.name, + targetName: selectedTargetName, + response, + sessionId: aguiResult.sessionId, + logFilePath: logger.logFilePath, + }; + } + return { - success: !hasError, + success: true, agentName: agentSpec.name, targetName: selectedTargetName, response, @@ -432,7 +506,10 @@ export async function handleInvoke(context: InvokeContext, options: InvokeOption }; } catch (err) { logger.logError(err, 'AGUI invoke failed'); - return { success: false, error: `AGUI invoke failed: ${err instanceof Error ? err.message : String(err)}` }; + return { + success: false, + error: new Error(`AGUI invoke failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err }), + }; } } diff --git a/src/cli/commands/invoke/command.tsx b/src/cli/commands/invoke/command.tsx index 390fe7b2e..9503d080b 100644 --- a/src/cli/commands/invoke/command.tsx +++ b/src/cli/commands/invoke/command.tsx @@ -1,3 +1,4 @@ +import { serializeResult } from '../../../lib'; import { getErrorMessage } from '../../errors'; import { COMMAND_DESCRIPTIONS } from '../../tui/copy'; import { requireProject, requireTTY } from '../../tui/guards'; @@ -79,7 +80,7 @@ async function handleInvokeCLI(options: InvokeOptions): Promise { } if (options.json) { - console.log(JSON.stringify(result)); + console.log(JSON.stringify(serializeResult(result))); } else if (options.stream) { // Streaming already wrote to stdout, just show session and log path if (result.sessionId) { @@ -94,7 +95,7 @@ async function handleInvokeCLI(options: InvokeOptions): Promise { if (result.success && result.response) { console.log(result.response); } else if (!result.success && result.error) { - console.error(result.error); + console.error(result.error.message); } if (result.sessionId) { console.error(`\nSession: ${result.sessionId}`); diff --git a/src/cli/commands/invoke/types.ts b/src/cli/commands/invoke/types.ts index d88dc5f69..31798b3de 100644 --- a/src/cli/commands/invoke/types.ts +++ b/src/cli/commands/invoke/types.ts @@ -1,3 +1,5 @@ +import type { Result } from '../../../lib/result'; + export interface InvokeOptions { agentName?: string; harnessName?: string; @@ -51,12 +53,10 @@ export interface InvokeOptions { actorId?: string; } -export interface InvokeResult { - success: boolean; +export type InvokeResult = Result & { + logFilePath?: string; agentName?: string; targetName?: string; response?: string; sessionId?: string; - error?: string; - logFilePath?: string; -} +}; diff --git a/src/cli/commands/logs/__tests__/action.test.ts b/src/cli/commands/logs/__tests__/action.test.ts index 842fa333a..714358767 100644 --- a/src/cli/commands/logs/__tests__/action.test.ts +++ b/src/cli/commands/logs/__tests__/action.test.ts @@ -60,7 +60,10 @@ describe('resolveAgentContext', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -125,7 +128,10 @@ describe('resolveAgentContext', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -134,9 +140,9 @@ describe('resolveAgentContext', () => { const result = await resolveAgentContext(context, {}); expect(result.success).toBe(false); if (!result.success) { - expect(result.error).toContain('Multiple runtimes found'); - expect(result.error).toContain('AgentA'); - expect(result.error).toContain('AgentB'); + expect(result.error.message).toContain('Multiple runtimes found'); + expect(result.error.message).toContain('AgentA'); + expect(result.error.message).toContain('AgentB'); } }); @@ -170,7 +176,10 @@ describe('resolveAgentContext', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -208,7 +217,7 @@ describe('resolveAgentContext', () => { const result = await resolveAgentContext(makeContext(), { runtime: 'UnknownAgent' }); expect(result.success).toBe(false); if (!result.success) { - expect(result.error).toContain("Runtime 'UnknownAgent' not found"); + expect(result.error.message).toContain("Runtime 'UnknownAgent' not found"); } }); @@ -225,7 +234,10 @@ describe('resolveAgentContext', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -234,7 +246,11 @@ describe('resolveAgentContext', () => { const result = await resolveAgentContext(context, {}); expect(result.success).toBe(false); if (!result.success) { +<<<<<<< HEAD expect(result.error).toContain('No runtimes or harnesses defined'); +======= + expect(result.error.message).toContain('No runtimes defined'); +>>>>>>> origin/main } }); @@ -253,7 +269,7 @@ describe('resolveAgentContext', () => { const result = await resolveAgentContext(context, {}); expect(result.success).toBe(false); if (!result.success) { - expect(result.error).toContain('is not deployed'); + expect(result.error.message).toContain('is not deployed'); } }); }); diff --git a/src/cli/commands/logs/action.ts b/src/cli/commands/logs/action.ts index 26d48f6f0..448d1ad48 100644 --- a/src/cli/commands/logs/action.ts +++ b/src/cli/commands/logs/action.ts @@ -1,3 +1,5 @@ +import { ResourceNotFoundError, ValidationError } from '../../../lib'; +import type { Result } from '../../../lib/result'; import { parseTimeString } from '../../../lib/utils'; import { searchLogs, streamLogs } from '../../aws/cloudwatch'; import { DEFAULT_ENDPOINT_NAME } from '../../constants'; @@ -17,11 +19,6 @@ export interface AgentContext { logGroupName: string; } -export interface LogsResult { - success: boolean; - error?: string; -} - /** * Detect whether to stream or search based on options */ @@ -50,10 +47,15 @@ export function formatLogLine(event: { timestamp: number; message: string }, jso export async function resolveAgentContext( context: DeployedProjectConfig, options: LogsOptions +<<<<<<< HEAD ): Promise<{ success: true; agentContext: AgentContext } | { success: false; error: string }> { const result = await resolveAgentOrHarness(context, options); +======= +): Result<{ agentContext: AgentContext }> { + const result = resolveAgent(context, options); +>>>>>>> origin/main if (!result.success) { - return { success: false, error: result.error }; + return { success: false, error: new ResourceNotFoundError(result.error) }; } const { agent } = result; const endpointName = DEFAULT_ENDPOINT_NAME; @@ -74,12 +76,12 @@ export async function resolveAgentContext( /** * Main logs handler */ -export async function handleLogs(options: LogsOptions): Promise { +export async function handleLogs(options: LogsOptions): Promise { // Validate level early if (options.level && !VALID_LEVELS.includes(options.level.toLowerCase())) { return { success: false, - error: `Invalid log level: "${options.level}". Valid levels: ${VALID_LEVELS.join(', ')}`, + error: new ValidationError(`Invalid log level: "${options.level}". Valid levels: ${VALID_LEVELS.join(', ')}`), }; } @@ -97,7 +99,7 @@ export async function handleLogs(options: LogsOptions): Promise { try { filterPattern = buildFilterPattern({ level: options.level, query: options.query }); } catch (err) { - return { success: false, error: (err as Error).message }; + return { success: false, error: err instanceof Error ? err : new Error(String(err)) }; } const mode = detectMode(options); @@ -144,7 +146,9 @@ export async function handleLogs(options: LogsOptions): Promise { if (errorName === 'ResourceNotFoundException') { return { success: false, - error: `No logs found for agent '${agentContext.agentName}'. Has the agent been invoked?`, + error: new ResourceNotFoundError( + `No logs found for agent '${agentContext.agentName}'. Has the agent been invoked?` + ), }; } diff --git a/src/cli/commands/logs/command.tsx b/src/cli/commands/logs/command.tsx index 12728d995..3beb18346 100644 --- a/src/cli/commands/logs/command.tsx +++ b/src/cli/commands/logs/command.tsx @@ -35,7 +35,7 @@ export const registerLogs = (program: Command) => { const result = await handleLogs(cliOptions); if (!result.success) { - render({result.error}); + render({result.error.message}); process.exit(1); } } catch (error) { @@ -60,7 +60,7 @@ export const registerLogs = (program: Command) => { const result = await handleLogsEval(cliOptions); if (!result.success) { - render({result.error}); + render({result.error.message}); process.exit(1); } } catch (error) { diff --git a/src/cli/commands/pause/command.tsx b/src/cli/commands/pause/command.tsx index e99e266aa..38bbdd4a5 100644 --- a/src/cli/commands/pause/command.tsx +++ b/src/cli/commands/pause/command.tsx @@ -1,4 +1,8 @@ +<<<<<<< HEAD import { ConfigIO } from '../../../lib'; +======= +import { ConfigIO, serializeResult } from '../../../lib'; +>>>>>>> origin/main import { listABTests, updateABTest } from '../../aws/agentcore-ab-tests'; import { stopBatchEvaluation } from '../../aws/agentcore-batch-evaluation'; import { getErrorMessage } from '../../errors'; @@ -52,12 +56,12 @@ function registerOnlineEvalSubcommand(parent: Command, action: 'pause' | 'resume const result = await handlePauseResume(options, action); if (cliOptions.json) { - console.log(JSON.stringify(result)); + console.log(JSON.stringify(serializeResult(result))); } else if (result.success) { const displayName = cliOptions.arn ? result.configId : name; console.log(`${pastTense} online eval config "${displayName}" (status: ${result.executionStatus})`); } else { - render({result.error}); + render({result.error.message}); } process.exit(result.success ? 0 : 1); diff --git a/src/cli/commands/remove/command.tsx b/src/cli/commands/remove/command.tsx index 3705c523f..192b93dff 100644 --- a/src/cli/commands/remove/command.tsx +++ b/src/cli/commands/remove/command.tsx @@ -1,5 +1,6 @@ -import { ConfigIO } from '../../../lib'; +import { ConfigIO, serializeResult, toError } from '../../../lib'; import { getErrorMessage } from '../../errors'; +import { runCliCommand } from '../../telemetry/cli-command-run.js'; import { COMMAND_DESCRIPTIONS } from '../../tui/copy'; import { requireProject, requireTTY } from '../../tui/guards'; import { RemoveAllScreen, RemoveFlow } from '../../tui/screens/remove'; @@ -34,7 +35,10 @@ async function handleRemoveAll(_options: RemoveAllOptions): Promise>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -49,15 +53,18 @@ async function handleRemoveAll(_options: RemoveAllOptions): Promise { validateRemoveAllOptions(options); - const result = await handleRemoveAll(options); - console.log(JSON.stringify(result)); - process.exit(result.success ? 0 : 1); + await runCliCommand('remove.all', !!options.json, async () => { + const result = await handleRemoveAll(options); + if (!result.success) throw result.error; + console.log(JSON.stringify(serializeResult(result))); + return {}; + }); } export const registerRemove = (program: Command): Command => { diff --git a/src/cli/commands/remove/types.ts b/src/cli/commands/remove/types.ts index 06089bfeb..e2ab9b09f 100644 --- a/src/cli/commands/remove/types.ts +++ b/src/cli/commands/remove/types.ts @@ -1,3 +1,5 @@ +import type { Result } from '../../../lib/result'; + export type ResourceType = | 'agent' | 'gateway' @@ -26,11 +28,9 @@ export interface RemoveAllOptions { json?: boolean; } -export interface RemoveResult { - success: boolean; +export type RemoveResult = Result<{ resourceType?: ResourceType; resourceName?: string; message?: string; note?: string; - error?: string; -} +}>; diff --git a/src/cli/commands/run/command.tsx b/src/cli/commands/run/command.tsx index e5ba8ca59..503ed71b3 100644 --- a/src/cli/commands/run/command.tsx +++ b/src/cli/commands/run/command.tsx @@ -1,3 +1,7 @@ +<<<<<<< HEAD +======= +import { serializeResult } from '../../../lib'; +>>>>>>> origin/main import type { RecommendationType } from '../../aws/agentcore-recommendation'; import { getErrorMessage } from '../../errors'; import { handleRunEval } from '../../operations/eval'; @@ -25,7 +29,7 @@ const RECOMMENDATION_TYPE_MAP: Record = { }; function formatRunOutput(result: Awaited>): void { - if (!result.run) return; + if (!result.success) return; const { run } = result; const date = new Date(run.timestamp).toLocaleString([], { @@ -56,7 +60,7 @@ function formatRunOutput(result: Awaited>): voi for (const r of run.results) { const score = r.aggregateScore.toFixed(2); - const errors = r.sessionScores.filter(s => s.errorMessage).length; + const errors = r.sessionScores.filter((s: { errorMessage?: string }) => s.errorMessage).length; const errorSuffix = errors > 0 ? ` (${errors} errors)` : ''; console.log(` ${r.evaluator}: ${score}${errorSuffix}`); } @@ -146,12 +150,12 @@ export const registerRun = (program: Command) => { const result = await handleRunEval(options); if (cliOptions.json) { - console.log(JSON.stringify(result)); + console.log(JSON.stringify(serializeResult(result))); } else if (result.success) { formatRunOutput(result); } else { formatRunOutput(result); - render({result.error}); + render({result.error.message}); } process.exit(result.success ? 0 : 1); @@ -240,11 +244,19 @@ export const registerRun = (program: Command) => { } if (cliOptions.json) { +<<<<<<< HEAD console.log(JSON.stringify(result)); } else if (result.success) { formatBatchEvalOutput(result); } else { render({result.error}); +======= + console.log(JSON.stringify(serializeResult(result))); + } else if (result.success) { + formatBatchEvalOutput(result); + } else { + render({result.error.message}); +>>>>>>> origin/main if (result.logFilePath) { console.error(`\nLog: ${result.logFilePath}`); } @@ -401,9 +413,15 @@ export const registerRun = (program: Command) => { if (!result.success) { if (cliOptions.json) { +<<<<<<< HEAD console.log(JSON.stringify(result)); } else { render({result.error}); +======= + console.log(JSON.stringify(serializeResult(result))); + } else { + render({result.error.message}); +>>>>>>> origin/main if (result.logFilePath) { console.error(`\nLog: ${result.logFilePath}`); } @@ -428,7 +446,11 @@ export const registerRun = (program: Command) => { } if (cliOptions.json) { +<<<<<<< HEAD console.log(JSON.stringify(result)); +======= + console.log(JSON.stringify(serializeResult(result))); +>>>>>>> origin/main } else { console.log(`\nRecommendation ID: ${result.recommendationId}`); @@ -467,7 +489,11 @@ export const registerRun = (program: Command) => { ); console.log(`Local config for "${cliOptions.bundleName}" has been updated to match.`); } else { +<<<<<<< HEAD console.log(`\nCould not sync config bundle: ${applyResult.error}`); +======= + console.log(`\nCould not sync config bundle: ${applyResult.error.message}`); +>>>>>>> origin/main } } catch { // Non-fatal — user can manually sync diff --git a/src/cli/commands/status/__tests__/action.test.ts b/src/cli/commands/status/__tests__/action.test.ts index 1c3a089dc..f8c94c6bc 100644 --- a/src/cli/commands/status/__tests__/action.test.ts +++ b/src/cli/commands/status/__tests__/action.test.ts @@ -1,7 +1,8 @@ import type { AgentCoreProjectSpec, DeployedResourceState } from '../../../../schema/index.js'; import { computeResourceStatuses, handleProjectStatus } from '../action.js'; -import type { StatusContext } from '../action.js'; +import type { ResourceStatusEntry, StatusContext } from '../action.js'; import { buildRuntimeInvocationUrl } from '../constants.js'; +import assert from 'node:assert'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; const mockGetAgentRuntimeStatus = vi.fn(); @@ -508,9 +509,11 @@ describe('handleProjectStatus — live enrichment', () => { const result = await handleProjectStatus(makeContext()); - expect(result.success).toBe(true); + assert(result.success); - const evalEntry = result.resources.find(r => r.resourceType === 'evaluator' && r.name === 'MyEval'); + const evalEntry = result.resources.find( + (r: ResourceStatusEntry) => r.resourceType === 'evaluator' && r.name === 'MyEval' + ); expect(evalEntry).toBeDefined(); expect(evalEntry!.detail).toContain('ACTIVE'); @@ -536,9 +539,11 @@ describe('handleProjectStatus — live enrichment', () => { const result = await handleProjectStatus(makeContext()); - expect(result.success).toBe(true); + assert(result.success); - const configEntry = result.resources.find(r => r.resourceType === 'online-eval' && r.name === 'MyConfig'); + const configEntry = result.resources.find( + (r: ResourceStatusEntry) => r.resourceType === 'online-eval' && r.name === 'MyConfig' + ); expect(configEntry).toBeDefined(); expect(configEntry!.detail).toContain('ACTIVE'); expect(configEntry!.detail).toContain('ENABLED'); @@ -560,9 +565,11 @@ describe('handleProjectStatus — live enrichment', () => { const result = await handleProjectStatus(makeContext()); - expect(result.success).toBe(true); + assert(result.success); - const evalEntry = result.resources.find(r => r.resourceType === 'evaluator' && r.name === 'MyEval'); + const evalEntry = result.resources.find( + (r: ResourceStatusEntry) => r.resourceType === 'evaluator' && r.name === 'MyEval' + ); expect(evalEntry).toBeDefined(); expect(evalEntry!.error).toBe('AccessDenied'); }); @@ -578,9 +585,11 @@ describe('handleProjectStatus — live enrichment', () => { const result = await handleProjectStatus(makeContext()); - expect(result.success).toBe(true); + assert(result.success); - const configEntry = result.resources.find(r => r.resourceType === 'online-eval' && r.name === 'MyConfig'); + const configEntry = result.resources.find( + (r: ResourceStatusEntry) => r.resourceType === 'online-eval' && r.name === 'MyConfig' + ); expect(configEntry).toBeDefined(); expect(configEntry!.error).toBe('ResourceNotFound'); }); @@ -624,9 +633,11 @@ describe('handleProjectStatus — live enrichment', () => { const result = await handleProjectStatus(ctx); - expect(result.success).toBe(true); + assert(result.success); - const evalEntry = result.resources.find(r => r.resourceType === 'evaluator' && r.name === 'MyEval'); + const evalEntry = result.resources.find( + (r: ResourceStatusEntry) => r.resourceType === 'evaluator' && r.name === 'MyEval' + ); expect(evalEntry).toBeDefined(); expect(evalEntry!.deploymentState).toBe('local-only'); expect(mockGetEvaluator).not.toHaveBeenCalled(); @@ -697,8 +708,10 @@ describe('handleProjectStatus — invocation URL enrichment', () => { const result = await handleProjectStatus(ctx); - expect(result.success).toBe(true); - const agentEntry = result.resources.find(r => r.resourceType === 'agent' && r.name === 'MyAgent'); + assert(result.success); + const agentEntry = result.resources.find( + (r: ResourceStatusEntry) => r.resourceType === 'agent' && r.name === 'MyAgent' + ); expect(agentEntry).toBeDefined(); expect(agentEntry!.invocationUrl).toBe( `https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/${encodeURIComponent(runtimeArn)}/invocations` @@ -723,8 +736,10 @@ describe('handleProjectStatus — invocation URL enrichment', () => { const result = await handleProjectStatus(ctx); - expect(result.success).toBe(true); - const agentEntry = result.resources.find(r => r.resourceType === 'agent' && r.name === 'LocalAgent'); + assert(result.success); + const agentEntry = result.resources.find( + (r: ResourceStatusEntry) => r.resourceType === 'agent' && r.name === 'LocalAgent' + ); expect(agentEntry).toBeDefined(); expect(agentEntry!.invocationUrl).toBeUndefined(); }); @@ -758,8 +773,10 @@ describe('handleProjectStatus — invocation URL enrichment', () => { const result = await handleProjectStatus(ctx); - expect(result.success).toBe(true); - const agentEntry = result.resources.find(r => r.resourceType === 'agent' && r.name === 'FailAgent'); + assert(result.success); + const agentEntry = result.resources.find( + (r: ResourceStatusEntry) => r.resourceType === 'agent' && r.name === 'FailAgent' + ); expect(agentEntry).toBeDefined(); expect(agentEntry!.error).toBe('Timeout'); expect(agentEntry!.invocationUrl).toBe( @@ -798,8 +815,10 @@ describe('handleProjectStatus — invocation URL enrichment', () => { const result = await handleProjectStatus(ctx); - expect(result.success).toBe(true); - const agentEntry = result.resources.find(r => r.resourceType === 'agent' && r.name === 'OldAgent'); + assert(result.success); + const agentEntry = result.resources.find( + (r: ResourceStatusEntry) => r.resourceType === 'agent' && r.name === 'OldAgent' + ); expect(agentEntry).toBeDefined(); expect(agentEntry!.deploymentState).toBe('pending-removal'); expect(agentEntry!.invocationUrl).toBeUndefined(); diff --git a/src/cli/commands/status/action.ts b/src/cli/commands/status/action.ts index 1ef72aaca..3b198606d 100644 --- a/src/cli/commands/status/action.ts +++ b/src/cli/commands/status/action.ts @@ -1,8 +1,12 @@ -import { ConfigIO } from '../../../lib'; +import { ConfigIO, ResourceNotFoundError, toError } from '../../../lib'; +import type { Result } from '../../../lib/result'; import type { AgentCoreProjectSpec, AwsDeploymentTargets, DeployedResourceState, DeployedState } from '../../../schema'; import { getAgentRuntimeStatus } from '../../aws'; import { getEvaluator, getOnlineEvaluationConfig } from '../../aws/agentcore-control'; +<<<<<<< HEAD import { getHarness } from '../../aws/agentcore-harness'; +======= +>>>>>>> origin/main import { dnsSuffix } from '../../aws/partition'; import { getErrorMessage } from '../../errors'; import { ExecLogger } from '../../logging'; @@ -34,15 +38,10 @@ export interface ResourceStatusEntry { invocationUrl?: string; } -export interface ProjectStatusResult { - success: boolean; - projectName: string; - targetName: string; +export type ProjectStatusResult = Result<{ targetRegion?: string; resources: ResourceStatusEntry[]; - error?: string; - logPath?: string; -} +}> & { projectName?: string; targetName?: string; logPath?: string; resources?: ResourceStatusEntry[] }; export interface StatusContext { project: AgentCoreProjectSpec; @@ -50,14 +49,11 @@ export interface StatusContext { awsTargets: AwsDeploymentTargets; } -export interface RuntimeLookupResult { - success: boolean; +export type RuntimeLookupResult = Result<{ targetName?: string; runtimeId?: string; runtimeStatus?: string; - error?: string; - logPath?: string; -} +}> & { logPath?: string }; /** * Loads configuration required for status check. @@ -343,10 +339,10 @@ export async function handleProjectStatus( logger.finalize(false); return { success: false, + error: new Error(error), projectName: project.name, targetName: options.targetName, resources: [], - error, logPath: logger.getRelativeLogPath(), }; } @@ -550,7 +546,7 @@ export async function handleRuntimeLookup( const error = 'No deployment targets found. Run `agentcore create` first.'; logger.endStep('error', error); logger.finalize(false); - return { success: false, error, logPath: logger.getRelativeLogPath() }; + return { success: false, error: new ResourceNotFoundError(error), logPath: logger.getRelativeLogPath() }; } const selectedTargetName = options.targetName ?? targetNames[0]!; @@ -559,7 +555,7 @@ export async function handleRuntimeLookup( const error = `Target '${options.targetName}' not found. Available: ${targetNames.join(', ')}`; logger.endStep('error', error); logger.finalize(false); - return { success: false, error, logPath: logger.getRelativeLogPath() }; + return { success: false, error: new ResourceNotFoundError(error), logPath: logger.getRelativeLogPath() }; } const targetConfig = awsTargets.find(target => target.name === selectedTargetName); @@ -568,7 +564,7 @@ export async function handleRuntimeLookup( const error = `Target config '${selectedTargetName}' not found in aws-targets`; logger.endStep('error', error); logger.finalize(false); - return { success: false, error, logPath: logger.getRelativeLogPath() }; + return { success: false, error: new ResourceNotFoundError(error), logPath: logger.getRelativeLogPath() }; } logger.log(`Target: ${selectedTargetName} (${targetConfig.region})`); @@ -596,6 +592,6 @@ export async function handleRuntimeLookup( const errorMsg = getErrorMessage(error); logger.endStep('error', errorMsg); logger.finalize(false); - return { success: false, error: errorMsg, logPath: logger.getRelativeLogPath() }; + return { success: false, error: toError(error), logPath: logger.getRelativeLogPath() }; } } diff --git a/src/cli/commands/status/command.tsx b/src/cli/commands/status/command.tsx index bd997a999..006e1bb39 100644 --- a/src/cli/commands/status/command.tsx +++ b/src/cli/commands/status/command.tsx @@ -1,3 +1,4 @@ +import { serializeResult } from '../../../lib'; import { getErrorMessage } from '../../errors'; import { COMMAND_DESCRIPTIONS } from '../../tui/copy'; import { requireProject } from '../../tui/guards'; @@ -62,7 +63,11 @@ export const registerStatus = (program: Command) => { .option('--target ', 'Select deployment target') .option( '--type ', +<<<<<<< HEAD 'Filter by resource type (agent, harness, runtime-endpoint, memory, credential, gateway, evaluator, online-eval, policy-engine, policy, config-bundle, ab-test)' +======= + 'Filter by resource type (agent, runtime-endpoint, memory, credential, gateway, evaluator, online-eval, policy-engine, policy, config-bundle, ab-test)' +>>>>>>> origin/main ) .option('--state ', 'Filter by deployment state (deployed, local-only, pending-removal)') .option('--runtime ', 'Filter to a specific runtime') @@ -101,12 +106,12 @@ export const registerStatus = (program: Command) => { }); if (cliOptions.json) { - console.log(JSON.stringify(result, null, 2)); + console.log(JSON.stringify(serializeResult(result), null, 2)); return; } if (!result.success) { - render({result.error}); + render({result.error.message}); return; } @@ -127,13 +132,17 @@ export const registerStatus = (program: Command) => { }); if (cliOptions.json) { - const filtered = filterResources(result.resources, cliOptions); - console.log(JSON.stringify({ ...result, resources: filtered }, null, 2)); + if (result.success) { + const filtered = filterResources(result.resources, cliOptions); + console.log(JSON.stringify({ ...result, resources: filtered }, null, 2)); + } else { + console.log(JSON.stringify(serializeResult(result), null, 2)); + } return; } if (!result.success) { - render({result.error}); + render({result.error.message}); return; } @@ -155,7 +164,11 @@ export const registerStatus = (program: Command) => { render( +<<<<<<< HEAD AgentCore Status (target: {result.targetName || 'No target configured'} +======= + AgentCore Status (target: {result.targetName ?? 'No target configured'} +>>>>>>> origin/main {result.targetRegion ? `, ${result.targetRegion}` : ''}) diff --git a/src/cli/commands/traces/action.ts b/src/cli/commands/traces/action.ts index 719245480..024548a17 100644 --- a/src/cli/commands/traces/action.ts +++ b/src/cli/commands/traces/action.ts @@ -1,17 +1,16 @@ +import { ResourceNotFoundError, ValidationError } from '../../../lib'; +import type { Result } from '../../../lib/result'; import { parseTimeString } from '../../../lib/utils'; import type { DeployedProjectConfig } from '../../operations/resolve-agent'; import { resolveAgentOrHarness } from '../../operations/resolve-agent'; import { buildTraceConsoleUrl, getTrace, listTraces } from '../../operations/traces'; import type { TracesGetOptions, TracesListOptions } from './types'; -export interface TracesListResult { - success: boolean; +export type TracesListResult = Result<{ agentName?: string; targetName?: string; - consoleUrl?: string; - traces?: { traceId: string; timestamp: string; sessionId?: string }[]; - error?: string; -} + traces: { traceId: string; timestamp: string; sessionId?: string }[]; +}> & { consoleUrl?: string }; export async function handleTracesList( context: DeployedProjectConfig, @@ -19,7 +18,7 @@ export async function handleTracesList( ): Promise { const resolved = await resolveAgentOrHarness(context, options); if (!resolved.success) { - return { success: false, error: resolved.error }; + return { success: false, error: new ResourceNotFoundError(resolved.error) }; } const { agent } = resolved; @@ -33,7 +32,7 @@ export async function handleTracesList( const limit = options.limit ? parseInt(options.limit, 10) : 20; if (isNaN(limit)) { - return { success: false, error: '--limit must be a number' }; + return { success: false, error: new ValidationError('--limit must be a number') }; } // Parse time options @@ -68,14 +67,11 @@ export async function handleTracesList( }; } -export interface TracesGetResult { - success: boolean; +export type TracesGetResult = Result<{ agentName?: string; targetName?: string; - consoleUrl?: string; filePath?: string; - error?: string; -} +}> & { consoleUrl?: string }; export async function handleTracesGet( context: DeployedProjectConfig, @@ -84,7 +80,7 @@ export async function handleTracesGet( ): Promise { const resolved = await resolveAgentOrHarness(context, options); if (!resolved.success) { - return { success: false, error: resolved.error }; + return { success: false, error: new ResourceNotFoundError(resolved.error) }; } const { agent } = resolved; diff --git a/src/cli/commands/traces/command.tsx b/src/cli/commands/traces/command.tsx index a25b8a613..0f2a11075 100644 --- a/src/cli/commands/traces/command.tsx +++ b/src/cli/commands/traces/command.tsx @@ -38,7 +38,7 @@ export const registerTraces = (program: Command) => { if (!result.success) { render( - Error: {result.error} + Error: {result.error.message} {result.consoleUrl && Console: {result.consoleUrl}} ); @@ -111,7 +111,7 @@ export const registerTraces = (program: Command) => { if (!result.success) { render( - Error: {result.error} + Error: {result.error.message} {result.consoleUrl && Console: {result.consoleUrl}} ); diff --git a/src/cli/commands/validate/__tests__/action.test.ts b/src/cli/commands/validate/__tests__/action.test.ts index a27894f64..6944f9132 100644 --- a/src/cli/commands/validate/__tests__/action.test.ts +++ b/src/cli/commands/validate/__tests__/action.test.ts @@ -1,4 +1,5 @@ import { handleValidate } from '../action.js'; +import assert from 'node:assert'; import { afterEach, describe, expect, it, vi } from 'vitest'; const { @@ -77,7 +78,8 @@ describe('handleValidate', () => { const result = await handleValidate({}); expect(result.success).toBe(false); - expect(result.error).toContain('No agentcore project found'); + assert(!result.success); + expect(result.error.message).toContain('No agentcore project found'); }); it('returns success when all configs are valid', async () => { @@ -98,7 +100,8 @@ describe('handleValidate', () => { const result = await handleValidate({}); expect(result.success).toBe(false); - expect(result.error).toContain('invalid project'); + assert(!result.success); + expect(result.error.message).toContain('invalid project'); }); it('returns error when AWS targets fails', async () => { @@ -109,7 +112,8 @@ describe('handleValidate', () => { const result = await handleValidate({}); expect(result.success).toBe(false); - expect(result.error).toContain('bad targets'); + assert(!result.success); + expect(result.error.message).toContain('bad targets'); }); it('validates state file when it exists', async () => { @@ -135,7 +139,8 @@ describe('handleValidate', () => { const result = await handleValidate({}); expect(result.success).toBe(false); - expect(result.error).toContain('bad state'); + assert(!result.success); + expect(result.error.message).toContain('bad state'); }); it('uses custom directory when provided', async () => { @@ -158,7 +163,8 @@ describe('handleValidate', () => { const result = await handleValidate({}); expect(result.success).toBe(false); - expect(result.error).toBe('field "name" is required'); + assert(!result.success); + expect(result.error.message).toBe('field "name" is required'); }); it('formats ConfigParseError with cause', async () => { @@ -169,8 +175,9 @@ describe('handleValidate', () => { const result = await handleValidate({}); expect(result.success).toBe(false); - expect(result.error).toContain('Invalid JSON in agentcore.json'); - expect(result.error).toContain('Unexpected token'); + assert(!result.success); + expect(result.error.message).toContain('Invalid JSON in agentcore.json'); + expect(result.error.message).toContain('Unexpected token'); }); it('formats ConfigReadError with cause', async () => { @@ -183,8 +190,9 @@ describe('handleValidate', () => { const result = await handleValidate({}); expect(result.success).toBe(false); - expect(result.error).toContain('Failed to read agentcore.json'); - expect(result.error).toContain('EACCES'); + assert(!result.success); + expect(result.error.message).toContain('Failed to read agentcore.json'); + expect(result.error.message).toContain('EACCES'); }); it('formats ConfigNotFoundError with file name', async () => { @@ -195,7 +203,8 @@ describe('handleValidate', () => { const result = await handleValidate({}); expect(result.success).toBe(false); - expect(result.error).toBe('Required file not found: agentcore.json'); + assert(!result.success); + expect(result.error.message).toBe('Required file not found: agentcore.json'); }); it('formats non-Error values as strings', async () => { @@ -205,7 +214,8 @@ describe('handleValidate', () => { const result = await handleValidate({}); expect(result.success).toBe(false); - expect(result.error).toBe('string error'); + assert(!result.success); + expect(result.error.message).toBe('string error'); }); describe('harness validation', () => { diff --git a/src/cli/commands/validate/action.ts b/src/cli/commands/validate/action.ts index 410056750..b5d384c89 100644 --- a/src/cli/commands/validate/action.ts +++ b/src/cli/commands/validate/action.ts @@ -7,22 +7,21 @@ import { NoProjectError, findConfigRoot, } from '../../../lib'; +<<<<<<< HEAD import type { AgentCoreProjectSpec } from '../../../schema'; +======= +import type { Result } from '../../../lib/result'; +>>>>>>> origin/main export interface ValidateOptions { directory?: string; } -export interface ValidateResult { - success: boolean; - error?: string; -} - /** * Validates all AgentCore schema files in the project. * Returns a binary success/fail result with an error message if validation fails. */ -export async function handleValidate(options: ValidateOptions): Promise { +export async function handleValidate(options: ValidateOptions): Promise { const baseDir = options.directory ?? process.cwd(); // Check if project exists @@ -30,7 +29,7 @@ export async function handleValidate(options: ValidateOptions): Promise { render(Valid); process.exit(0); } else { - render({result.error}); + render({result.error.message}); process.exit(1); } }); diff --git a/src/cli/commands/validate/index.ts b/src/cli/commands/validate/index.ts index cecbb46ac..067c35e56 100644 --- a/src/cli/commands/validate/index.ts +++ b/src/cli/commands/validate/index.ts @@ -1,2 +1,2 @@ export { registerValidate } from './command'; -export { handleValidate, type ValidateOptions, type ValidateResult } from './action'; +export { handleValidate, type ValidateOptions } from './action'; diff --git a/src/cli/errors.ts b/src/cli/errors.ts index 9e7e9397d..99c59c051 100644 --- a/src/cli/errors.ts +++ b/src/cli/errors.ts @@ -1,13 +1,3 @@ -/** - * Error thrown when an agent with the same name already exists. - */ -export class AgentAlreadyExistsError extends Error { - constructor(agentName: string) { - super(`An agent named "${agentName}" already exists in the schema.`); - this.name = 'AgentAlreadyExistsError'; - } -} - /** * Converts an unknown error to a string message. * Handles Error instances and other thrown values consistently. diff --git a/src/cli/external-requirements/__tests__/checks-extended.test.ts b/src/cli/external-requirements/__tests__/checks-extended.test.ts index ecd4fe526..a44a3d763 100644 --- a/src/cli/external-requirements/__tests__/checks-extended.test.ts +++ b/src/cli/external-requirements/__tests__/checks-extended.test.ts @@ -53,7 +53,10 @@ describe('requiresUv', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -82,7 +85,10 @@ describe('requiresUv', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -102,7 +108,10 @@ describe('requiresUv', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -133,7 +142,10 @@ describe('requiresContainerRuntime', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -162,7 +174,10 @@ describe('requiresContainerRuntime', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -182,7 +197,10 @@ describe('requiresContainerRuntime', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -219,7 +237,10 @@ describe('requiresContainerRuntime', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -290,7 +311,10 @@ describe('checkDependencyVersions', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -314,7 +338,10 @@ describe('checkDependencyVersions', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -346,7 +373,10 @@ describe('checkDependencyVersions', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], diff --git a/src/cli/operations/agent/generate/write-agent-to-project.ts b/src/cli/operations/agent/generate/write-agent-to-project.ts index 728236e54..e7f5cdb69 100644 --- a/src/cli/operations/agent/generate/write-agent-to-project.ts +++ b/src/cli/operations/agent/generate/write-agent-to-project.ts @@ -1,7 +1,6 @@ -import { ConfigIO, requireConfigRoot } from '../../../../lib'; +import { AgentAlreadyExistsError, ConfigIO, requireConfigRoot } from '../../../../lib'; import type { AgentCoreProjectSpec } from '../../../../schema'; import { SCHEMA_VERSION } from '../../../constants'; -import { AgentAlreadyExistsError } from '../../../errors'; import type { CredentialStrategy } from '../../../primitives/CredentialPrimitive'; import type { GenerateConfig } from '../../../tui/screens/generate/types'; import { mapGenerateConfigToAgent, mapGenerateInputToMemories, mapModelProviderToCredentials } from './schema-mapper'; @@ -72,7 +71,10 @@ export async function writeAgentToProject(config: GenerateConfig, options?: Writ onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], diff --git a/src/cli/operations/agent/import/index.ts b/src/cli/operations/agent/import/index.ts index e49d9e3c0..25fb6299f 100644 --- a/src/cli/operations/agent/import/index.ts +++ b/src/cli/operations/agent/import/index.ts @@ -3,10 +3,9 @@ * Provides executeImportAgent() as the shared handler called by both * the TUI hook (useAddAgent) and the non-interactive CLI (AgentPrimitive). */ -import { APP_DIR, ConfigIO } from '../../../../lib'; +import { APP_DIR, ConfigIO, toError } from '../../../../lib'; import type { RuntimeAuthorizerType, SDKFramework } from '../../../../schema'; import { getBedrockAgentConfig } from '../../../aws/bedrock-import'; -import { getErrorMessage } from '../../../errors'; import type { JwtConfigOptions } from '../../../primitives/auth-utils'; import { createManagedOAuthCredential } from '../../../primitives/auth-utils'; import type { AddResult } from '../../../primitives/types'; @@ -120,6 +119,6 @@ export async function executeImportAgent( return { success: true, agentName: name, agentPath }; } catch (err) { - return { success: false, error: getErrorMessage(err) }; + return { success: false, error: toError(err) }; } } diff --git a/src/cli/operations/deploy/__tests__/post-deploy-ab-tests.test.ts b/src/cli/operations/deploy/__tests__/post-deploy-ab-tests.test.ts index 5e30115c0..33f66f5d5 100644 --- a/src/cli/operations/deploy/__tests__/post-deploy-ab-tests.test.ts +++ b/src/cli/operations/deploy/__tests__/post-deploy-ab-tests.test.ts @@ -66,7 +66,10 @@ function makeProjectSpec(abTests: AgentCoreProjectSpec['abTests'] = []): AgentCo onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], httpGateways: [], abTests, @@ -299,6 +302,70 @@ describe('setupABTests', () => { expect(mockCreateABTest.mock.calls[0]![0].evaluationConfig.onlineEvaluationConfigArn).toBe('arn:eval:resolved'); }); +<<<<<<< HEAD +======= + + it('resolves target-based variant with project prefix (runtime === projectName)', async () => { + const targetBasedTest = { + ...sampleABTest, + mode: 'target-based' as const, + variants: [ + { + name: 'C' as const, + weight: 90, + variantConfiguration: { target: { targetName: 'MyAgent-prod' } }, + }, + { + name: 'T1' as const, + weight: 10, + variantConfiguration: { target: { targetName: 'MyAgent-staging' } }, + }, + ], + }; + mockCreateABTest.mockResolvedValue({ abTestId: 'abt-tgt-1', abTestArn: 'arn:abt:tgt1' }); + + await setupABTests({ + region: 'us-east-1', + projectSpec: makeProjectSpec([targetBasedTest]), + }); + + const callArgs = mockCreateABTest.mock.calls[0]![0]; + expect(callArgs.variants[0].variantConfiguration.target.name).toBe('TestProject-MyAgent-prod'); + expect(callArgs.variants[1].variantConfiguration.target.name).toBe('TestProject-MyAgent-staging'); + }); + + it('resolves target-based variant with project prefix (different project name)', async () => { + const targetBasedTest = { + ...sampleABTest, + mode: 'target-based' as const, + variants: [ + { + name: 'C' as const, + weight: 90, + variantConfiguration: { target: { targetName: 'Bar-prod' } }, + }, + { + name: 'T1' as const, + weight: 10, + variantConfiguration: { target: { targetName: 'Bar-staging' } }, + }, + ], + }; + mockCreateABTest.mockResolvedValue({ abTestId: 'abt-tgt-2', abTestArn: 'arn:abt:tgt2' }); + + const spec = makeProjectSpec([targetBasedTest]); + spec.name = 'Foo'; + + await setupABTests({ + region: 'us-east-1', + projectSpec: spec, + }); + + const callArgs = mockCreateABTest.mock.calls[0]![0]; + expect(callArgs.variants[0].variantConfiguration.target.name).toBe('Foo-Bar-prod'); + expect(callArgs.variants[1].variantConfiguration.target.name).toBe('Foo-Bar-staging'); + }); +>>>>>>> origin/main }); describe('deletion (reconciliation)', () => { diff --git a/src/cli/operations/deploy/__tests__/post-deploy-config-bundles.test.ts b/src/cli/operations/deploy/__tests__/post-deploy-config-bundles.test.ts index 34be18b88..4f4420953 100644 --- a/src/cli/operations/deploy/__tests__/post-deploy-config-bundles.test.ts +++ b/src/cli/operations/deploy/__tests__/post-deploy-config-bundles.test.ts @@ -504,7 +504,10 @@ describe('resolveConfigBundleComponentKeys', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles, httpGateways: [], abTests: [], diff --git a/src/cli/operations/deploy/__tests__/post-deploy-http-gateways.test.ts b/src/cli/operations/deploy/__tests__/post-deploy-http-gateways.test.ts index bb937cfd7..f095b803f 100644 --- a/src/cli/operations/deploy/__tests__/post-deploy-http-gateways.test.ts +++ b/src/cli/operations/deploy/__tests__/post-deploy-http-gateways.test.ts @@ -78,7 +78,10 @@ function makeProjectSpec(httpGateways: AgentCoreProjectSpec['httpGateways'] = [] onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways, diff --git a/src/cli/operations/deploy/__tests__/post-deploy-observability.test.ts b/src/cli/operations/deploy/__tests__/post-deploy-observability.test.ts index ba069f29e..8fc549624 100644 --- a/src/cli/operations/deploy/__tests__/post-deploy-observability.test.ts +++ b/src/cli/operations/deploy/__tests__/post-deploy-observability.test.ts @@ -70,7 +70,7 @@ describe('setupTransactionSearch', () => { }); it('propagates error from enableTransactionSearch', async () => { - mockEnableTransactionSearch.mockResolvedValue({ success: false, error: 'Insufficient permissions' }); + mockEnableTransactionSearch.mockResolvedValue({ success: false, error: new Error('Insufficient permissions') }); const result = await setupTransactionSearch({ region: 'us-east-1', @@ -78,7 +78,10 @@ describe('setupTransactionSearch', () => { agentNames: ['agent-1'], }); - expect(result).toEqual({ success: false, error: 'Insufficient permissions' }); + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.message).toBe('Insufficient permissions'); + } }); it('triggers when hasGateways is true and agentNames is empty', async () => { diff --git a/src/cli/operations/deploy/index.ts b/src/cli/operations/deploy/index.ts index 332f0ca2d..7418e90cd 100644 --- a/src/cli/operations/deploy/index.ts +++ b/src/cli/operations/deploy/index.ts @@ -39,11 +39,34 @@ export { type DeployedTarget, type DiscoverDeployedResult, type DestroyTargetOptions, - type StackTeardownResult, } from './teardown'; // Post-deploy observability setup -export { setupTransactionSearch, type TransactionSearchSetupResult } from './post-deploy-observability'; +export { setupTransactionSearch } from './post-deploy-observability'; + +// Post-deploy HTTP gateways +export { + setupHttpGateways, + type SetupHttpGatewaysOptions, + type SetupHttpGatewaysResult, + type HttpGatewaySetupResult, +} from './post-deploy-http-gateways'; + +// Post-deploy online eval enablement +export { + enableOnlineEvalConfigs, + type EnableOnlineEvalsOptions, + type EnableOnlineEvalsResult, + type OnlineEvalEnableResult, +} from './post-deploy-online-evals'; + +// Post-deploy config bundles +export { + setupConfigBundles, + type SetupConfigBundlesOptions, + type SetupConfigBundlesResult, + type ConfigBundleSetupResult, +} from './post-deploy-config-bundles'; // Post-deploy HTTP gateways export { diff --git a/src/cli/operations/deploy/post-deploy-observability.ts b/src/cli/operations/deploy/post-deploy-observability.ts index 0616a65dc..e811ba983 100644 --- a/src/cli/operations/deploy/post-deploy-observability.ts +++ b/src/cli/operations/deploy/post-deploy-observability.ts @@ -1,3 +1,7 @@ +<<<<<<< HEAD +======= +import type { Result } from '../../../lib/result'; +>>>>>>> origin/main import { readGlobalConfigSync } from '../../../lib/schemas/io/global-config'; import { enableTransactionSearch } from '../../aws/transaction-search'; @@ -8,11 +12,6 @@ export interface TransactionSearchSetupOptions { hasGateways?: boolean; } -export interface TransactionSearchSetupResult { - success: boolean; - error?: string; -} - /** * Post-deploy step: enable CloudWatch Transaction Search (Application Signals + * resource policy + CloudWatchLogs destination + 100% indexing). @@ -22,9 +21,7 @@ export interface TransactionSearchSetupResult { * * This is a non-blocking best-effort operation — failures do not fail the deploy. */ -export async function setupTransactionSearch( - options: TransactionSearchSetupOptions -): Promise { +export async function setupTransactionSearch(options: TransactionSearchSetupOptions): Promise { const { region, accountId, agentNames } = options; if (agentNames.length === 0 && !options.hasGateways) { diff --git a/src/cli/operations/deploy/preflight.ts b/src/cli/operations/deploy/preflight.ts index 7ef92554b..0eb2f79fa 100644 --- a/src/cli/operations/deploy/preflight.ts +++ b/src/cli/operations/deploy/preflight.ts @@ -116,6 +116,9 @@ export async function validateProject(): Promise { // Validate HTTP gateway names don't exceed AWS limits when combined with project name validateHttpGatewayNames(projectSpec); + // Validate HTTP gateway names don't exceed AWS limits when combined with project name + validateHttpGatewayNames(projectSpec); + // Validate Container agents have Dockerfiles validateContainerAgents(projectSpec, configRoot); @@ -152,6 +155,7 @@ function validateRuntimeNames(projectSpec: AgentCoreProjectSpec): void { } /** +<<<<<<< HEAD * Validates that combined harness names (projectName_harnessName) don't exceed AWS limits. */ function validateHarnessNames(projectSpec: AgentCoreProjectSpec): void { @@ -172,6 +176,8 @@ function validateHarnessNames(projectSpec: AgentCoreProjectSpec): void { } /** +======= +>>>>>>> origin/main * Validates that combined HTTP gateway names (projectName-gatewayName) don't exceed AWS limits. */ function validateHttpGatewayNames(projectSpec: AgentCoreProjectSpec): void { diff --git a/src/cli/operations/deploy/teardown.ts b/src/cli/operations/deploy/teardown.ts index 2e38f2576..b7c39c49b 100644 --- a/src/cli/operations/deploy/teardown.ts +++ b/src/cli/operations/deploy/teardown.ts @@ -1,4 +1,5 @@ import { CONFIG_DIR, ConfigIO } from '../../../lib'; +import type { Result } from '../../../lib/result'; import type { AwsDeploymentTarget } from '../../../schema'; import { withTargetRegion } from '../../aws'; import { deleteConfigurationBundle } from '../../aws/agentcore-config-bundles'; @@ -99,16 +100,11 @@ export function getCdkProjectDir(cwd?: string): string { return join(baseDir, CONFIG_DIR, 'cdk'); } -export interface StackTeardownResult { - success: boolean; - error?: string; -} - /** * Perform full stack teardown for a target: destroy CloudFormation stack, * remove deployed-state entry, and remove the target from aws-targets.json. */ -export async function performStackTeardown(targetName: string): Promise { +export async function performStackTeardown(targetName: string): Promise { const cdkProjectDir = getCdkProjectDir(); const configIO = new ConfigIO(); diff --git a/src/cli/operations/dev/__tests__/config.test.ts b/src/cli/operations/dev/__tests__/config.test.ts index 75047d37d..a8884c38d 100644 --- a/src/cli/operations/dev/__tests__/config.test.ts +++ b/src/cli/operations/dev/__tests__/config.test.ts @@ -21,7 +21,10 @@ describe('getDevConfig', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -52,7 +55,10 @@ describe('getDevConfig', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -83,7 +89,10 @@ describe('getDevConfig', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -120,7 +129,10 @@ describe('getDevConfig', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -152,7 +164,10 @@ describe('getDevConfig', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -182,7 +197,10 @@ describe('getDevConfig', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -215,7 +233,10 @@ describe('getDevConfig', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -248,7 +269,10 @@ describe('getDevConfig', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -281,7 +305,10 @@ describe('getDevConfig', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -313,7 +340,10 @@ describe('getDevConfig', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -345,7 +375,10 @@ describe('getDevConfig', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -377,7 +410,10 @@ describe('getDevConfig', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -409,7 +445,10 @@ describe('getDevConfig', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -442,7 +481,10 @@ describe('getDevConfig', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -488,7 +530,10 @@ describe('getAgentPort', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -510,7 +555,10 @@ describe('getAgentPort', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -537,7 +585,10 @@ describe('getDevSupportedAgents', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -567,7 +618,10 @@ describe('getDevSupportedAgents', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -605,7 +659,10 @@ describe('getDevSupportedAgents', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -637,7 +694,10 @@ describe('getDevSupportedAgents', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -677,7 +737,10 @@ describe('getDevSupportedAgents', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], diff --git a/src/cli/operations/eval/__tests__/list-eval-runs.test.ts b/src/cli/operations/eval/__tests__/list-eval-runs.test.ts index c9a71a8cf..1c1c48f71 100644 --- a/src/cli/operations/eval/__tests__/list-eval-runs.test.ts +++ b/src/cli/operations/eval/__tests__/list-eval-runs.test.ts @@ -1,5 +1,6 @@ import { handleListEvalRuns } from '../list-eval-runs.js'; import type { EvalRunResult } from '../types.js'; +import assert from 'node:assert'; import { afterEach, describe, expect, it, vi } from 'vitest'; const mockListEvalRuns = vi.fn(); @@ -28,7 +29,7 @@ describe('handleListEvalRuns', () => { const result = handleListEvalRuns({}); - expect(result.success).toBe(true); + assert(result.success); expect(result.runs).toHaveLength(2); }); @@ -42,9 +43,9 @@ describe('handleListEvalRuns', () => { const result = handleListEvalRuns({ agent: 'agent-a' }); - expect(result.success).toBe(true); + assert(result.success); expect(result.runs).toHaveLength(2); - expect(result.runs!.every(r => r.agent === 'agent-a')).toBe(true); + expect(result.runs.every((r: EvalRunResult) => r.agent === 'agent-a')).toBe(true); }); it('limits the number of results', () => { @@ -57,7 +58,7 @@ describe('handleListEvalRuns', () => { const result = handleListEvalRuns({ limit: 2 }); - expect(result.success).toBe(true); + assert(result.success); expect(result.runs).toHaveLength(2); }); @@ -72,9 +73,10 @@ describe('handleListEvalRuns', () => { const result = handleListEvalRuns({ agent: 'a', limit: 2 }); + assert(result.success); expect(result.runs).toHaveLength(2); - expect(result.runs![0]!.timestamp).toBe('2025-01-15T10:00:00.000Z'); - expect(result.runs![1]!.timestamp).toBe('2025-01-15T12:00:00.000Z'); + expect(result.runs[0]!.timestamp).toBe('2025-01-15T10:00:00.000Z'); + expect(result.runs[1]!.timestamp).toBe('2025-01-15T12:00:00.000Z'); }); it('returns empty array when no runs exist', () => { @@ -82,7 +84,7 @@ describe('handleListEvalRuns', () => { const result = handleListEvalRuns({}); - expect(result.success).toBe(true); + assert(result.success); expect(result.runs).toEqual([]); }); @@ -93,9 +95,8 @@ describe('handleListEvalRuns', () => { const result = handleListEvalRuns({}); - expect(result.success).toBe(false); - expect(result.error).toBe('disk error'); - expect(result.runs).toBeUndefined(); + assert(!result.success); + expect(result.error.message).toBe('disk error'); }); it('handles non-Error thrown values', () => { @@ -105,7 +106,7 @@ describe('handleListEvalRuns', () => { const result = handleListEvalRuns({}); - expect(result.success).toBe(false); - expect(result.error).toBe('42'); + assert(!result.success); + expect(result.error.message).toBe('42'); }); }); diff --git a/src/cli/operations/eval/__tests__/logs-eval.test.ts b/src/cli/operations/eval/__tests__/logs-eval.test.ts index 5411d842e..a363941fd 100644 --- a/src/cli/operations/eval/__tests__/logs-eval.test.ts +++ b/src/cli/operations/eval/__tests__/logs-eval.test.ts @@ -1,4 +1,5 @@ import { handleLogsEval } from '../logs-eval.js'; +import assert from 'node:assert'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; const mockLoadDeployedProjectConfig = vi.fn(); @@ -100,8 +101,8 @@ describe('handleLogsEval', () => { const result = await handleLogsEval({}); - expect(result.success).toBe(false); - expect(result.error).toBe('No agents defined'); + assert(!result.success); + expect(result.error.message).toBe('No agents defined'); }); it('returns error when no online eval configs exist for the agent', async () => { @@ -111,8 +112,8 @@ describe('handleLogsEval', () => { const result = await handleLogsEval({}); - expect(result.success).toBe(false); - expect(result.error).toContain('No deployed online eval configs found'); + assert(!result.success); + expect(result.error.message).toContain('No deployed online eval configs found'); }); it('returns error when online eval configs exist but none are deployed', async () => { @@ -122,8 +123,8 @@ describe('handleLogsEval', () => { const result = await handleLogsEval({}); - expect(result.success).toBe(false); - expect(result.error).toContain('No deployed online eval configs found'); + assert(!result.success); + expect(result.error.message).toContain('No deployed online eval configs found'); }); it('searches logs with time range when --since is specified', async () => { diff --git a/src/cli/operations/eval/__tests__/pause-resume.test.ts b/src/cli/operations/eval/__tests__/pause-resume.test.ts index 834525675..9a6291a3d 100644 --- a/src/cli/operations/eval/__tests__/pause-resume.test.ts +++ b/src/cli/operations/eval/__tests__/pause-resume.test.ts @@ -1,4 +1,5 @@ import { handlePauseResume } from '../pause-resume.js'; +import assert from 'node:assert'; import { afterEach, describe, expect, it, vi } from 'vitest'; const mockLoadDeployedProjectConfig = vi.fn(); @@ -46,7 +47,7 @@ describe('handlePauseResume', () => { const result = await handlePauseResume({ name: 'my-config' }, 'pause'); - expect(result.success).toBe(true); + assert(result.success); expect(result.executionStatus).toBe('DISABLED'); expect(mockUpdateOnlineEvalExecutionStatus).toHaveBeenCalledWith({ region: 'us-east-1', @@ -65,7 +66,7 @@ describe('handlePauseResume', () => { const result = await handlePauseResume({ name: 'my-config' }, 'resume'); - expect(result.success).toBe(true); + assert(result.success); expect(result.executionStatus).toBe('ENABLED'); expect(mockUpdateOnlineEvalExecutionStatus).toHaveBeenCalledWith({ region: 'us-east-1', @@ -83,8 +84,8 @@ describe('handlePauseResume', () => { const result = await handlePauseResume({ name: 'my-config' }, 'pause'); - expect(result.success).toBe(false); - expect(result.error).toContain('No deployed targets found'); + assert(!result.success); + expect(result.error.message).toContain('No deployed targets found'); }); it('returns error when config name is not found in deployed state', async () => { @@ -92,9 +93,9 @@ describe('handlePauseResume', () => { const result = await handlePauseResume({ name: 'missing-config' }, 'pause'); - expect(result.success).toBe(false); - expect(result.error).toContain('missing-config'); - expect(result.error).toContain('not found'); + assert(!result.success); + expect(result.error.message).toContain('missing-config'); + expect(result.error.message).toContain('not found'); }); it('returns error when target config is missing from aws-targets', async () => { @@ -105,9 +106,9 @@ describe('handlePauseResume', () => { const result = await handlePauseResume({ name: 'my-config' }, 'pause'); - expect(result.success).toBe(false); - expect(result.error).toContain('Target config'); - expect(result.error).toContain('not found'); + assert(!result.success); + expect(result.error.message).toContain('Target config'); + expect(result.error.message).toContain('not found'); }); it('returns error when the SDK call fails', async () => { @@ -116,8 +117,8 @@ describe('handlePauseResume', () => { const result = await handlePauseResume({ name: 'my-config' }, 'pause'); - expect(result.success).toBe(false); - expect(result.error).toBe('Service unavailable'); + assert(!result.success); + expect(result.error.message).toBe('Service unavailable'); }); describe('ARN mode', () => { @@ -131,7 +132,7 @@ describe('handlePauseResume', () => { const arn = 'arn:aws:bedrock-agentcore:us-west-2:123456789012:online-evaluation-config/my-cfg-id'; const result = await handlePauseResume({ name: '', arn }, 'pause'); - expect(result.success).toBe(true); + assert(result.success); expect(result.executionStatus).toBe('DISABLED'); expect(mockLoadDeployedProjectConfig).not.toHaveBeenCalled(); expect(mockUpdateOnlineEvalExecutionStatus).toHaveBeenCalledWith({ @@ -151,7 +152,7 @@ describe('handlePauseResume', () => { const arn = 'arn:aws:bedrock-agentcore:us-west-2:123456789012:online-evaluation-config/my-cfg-id'; const result = await handlePauseResume({ name: '', arn, region: 'eu-west-1' }, 'resume'); - expect(result.success).toBe(true); + assert(result.success); expect(result.executionStatus).toBe('ENABLED'); expect(mockUpdateOnlineEvalExecutionStatus).toHaveBeenCalledWith({ region: 'eu-west-1', @@ -163,16 +164,16 @@ describe('handlePauseResume', () => { it('returns error for invalid ARN', async () => { const result = await handlePauseResume({ name: '', arn: 'not-an-arn' }, 'pause'); - expect(result.success).toBe(false); - expect(result.error).toContain('Invalid online eval config ARN'); + assert(!result.success); + expect(result.error.message).toContain('Invalid online eval config ARN'); }); it('returns error when config ID cannot be extracted from ARN', async () => { const arn = 'arn:aws:bedrock-agentcore:us-east-1:123456789012:some-other-resource/foo'; const result = await handlePauseResume({ name: '', arn }, 'pause'); - expect(result.success).toBe(false); - expect(result.error).toContain('Could not extract config ID'); + assert(!result.success); + expect(result.error.message).toContain('Could not extract config ID'); }); }); }); diff --git a/src/cli/operations/eval/__tests__/run-eval.test.ts b/src/cli/operations/eval/__tests__/run-eval.test.ts index 39314af69..7b6279b28 100644 --- a/src/cli/operations/eval/__tests__/run-eval.test.ts +++ b/src/cli/operations/eval/__tests__/run-eval.test.ts @@ -1,4 +1,5 @@ import { handleRunEval } from '../run-eval.js'; +import assert from 'node:assert'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; // ─── Mocks ──────────────────────────────────────────────────────────────────── @@ -150,8 +151,8 @@ describe('handleRunEval', () => { const result = await handleRunEval({ evaluator: ['Builtin.GoalSuccessRate'], days: 7 }); - expect(result.success).toBe(false); - expect(result.error).toBe('No agents defined'); + assert(!result.success); + expect(result.error.message).toBe('No agents defined'); }); it('returns error when a custom evaluator is not found in deployed state', async () => { @@ -170,9 +171,9 @@ describe('handleRunEval', () => { const result = await handleRunEval({ evaluator: ['MissingEval'], days: 7 }); - expect(result.success).toBe(false); - expect(result.error).toContain('MissingEval'); - expect(result.error).toContain('not found in deployed state'); + assert(!result.success); + expect(result.error.message).toContain('MissingEval'); + expect(result.error.message).toContain('not found in deployed state'); }); it('resolves builtin evaluators without deployed state lookup', async () => { @@ -195,7 +196,8 @@ describe('handleRunEval', () => { const result = await handleRunEval({ evaluator: ['Builtin.GoalSuccessRate'], days: 7 }); // Fails because no spans, but NOT because evaluator wasn't found - expect(result.error).toContain('No session spans found'); + assert(!result.success); + expect(result.error.message).toContain('No session spans found'); }); it('resolves custom evaluator name to deployed evaluator ID', async () => { @@ -278,9 +280,9 @@ describe('handleRunEval', () => { const result = await handleRunEval({ evaluator: ['Builtin.GoalSuccessRate'], days: 7 }); - expect(result.success).toBe(false); - expect(result.error).toContain('No session spans found'); - expect(result.error).toContain('my-agent'); + assert(!result.success); + expect(result.error.message).toContain('No session spans found'); + expect(result.error.message).toContain('my-agent'); }); // ─── Successful evaluation ──────────────────────────────────────────────── @@ -324,12 +326,12 @@ describe('handleRunEval', () => { const result = await handleRunEval({ evaluator: ['Builtin.GoalSuccessRate'], days: 7 }); - expect(result.success).toBe(true); + assert(result.success); expect(result.run).toBeDefined(); - expect(result.run!.sessionCount).toBe(2); - expect(result.run!.results).toHaveLength(1); + expect(result.run.sessionCount).toBe(2); + expect(result.run.results).toHaveLength(1); - const evalResult = result.run!.results[0]!; + const evalResult = result.run.results[0]!; expect(evalResult.aggregateScore).toBe(3.0); // (4 + 2) / 2 expect(evalResult.sessionScores).toHaveLength(2); expect(evalResult.tokenUsage).toEqual({ inputTokens: 180, outputTokens: 90, totalTokens: 270 }); @@ -361,8 +363,8 @@ describe('handleRunEval', () => { const result = await handleRunEval({ evaluator: ['Builtin.GoalSuccessRate'], days: 7 }); - expect(result.success).toBe(true); - const evalResult = result.run!.results[0]!; + assert(result.success); + const evalResult = result.run.results[0]!; // Only the non-errored session (value 5.0) should be in the aggregate expect(evalResult.aggregateScore).toBe(5.0); expect(evalResult.sessionScores).toHaveLength(2); @@ -391,7 +393,7 @@ describe('handleRunEval', () => { const result = await handleRunEval({ evaluator: ['Builtin.GoalSuccessRate'], days: 7 }); - expect(result.success).toBe(true); + assert(result.success); expect(mockSaveEvalRun).toHaveBeenCalled(); expect(mockWriteFileSync).not.toHaveBeenCalled(); expect(result.filePath).toBe('/tmp/eval-results/eval_2025-01-15_10-00-00.json'); @@ -422,7 +424,7 @@ describe('handleRunEval', () => { output: '/tmp/my-output.json', }); - expect(result.success).toBe(true); + assert(result.success); expect(mockWriteFileSync).toHaveBeenCalledWith('/tmp/my-output.json', expect.any(String)); expect(mockSaveEvalRun).not.toHaveBeenCalled(); expect(result.filePath).toBe('/tmp/my-output.json'); @@ -461,12 +463,12 @@ describe('handleRunEval', () => { days: 7, }); - expect(result.success).toBe(true); - expect(result.run!.results).toHaveLength(2); - expect(result.run!.results[0]!.evaluator).toBe('Builtin.GoalSuccessRate'); - expect(result.run!.results[0]!.aggregateScore).toBe(0.9); - expect(result.run!.results[1]!.evaluator).toBe('CustomEval'); - expect(result.run!.results[1]!.aggregateScore).toBe(4.5); + assert(result.success); + expect(result.run.results).toHaveLength(2); + expect(result.run.results[0]!.evaluator).toBe('Builtin.GoalSuccessRate'); + expect(result.run.results[0]!.aggregateScore).toBe(0.9); + expect(result.run.results[1]!.evaluator).toBe('CustomEval'); + expect(result.run.results[1]!.aggregateScore).toBe(4.5); }); // ─── ARN mode ───────────────────────────────────────────────────────────── @@ -484,8 +486,8 @@ describe('handleRunEval', () => { days: 3, }); - expect(result.success).toBe(true); - expect(result.run!.agent).toBe('rt-arn-test'); + assert(result.success); + expect(result.run.agent).toBe('rt-arn-test'); expect(mockLoadDeployedProjectConfig).not.toHaveBeenCalled(); expect(mockResolveAgent).not.toHaveBeenCalled(); }); @@ -532,8 +534,8 @@ describe('handleRunEval', () => { days: 7, }); - expect(result.success).toBe(false); - expect(result.error).toContain('Invalid agent runtime ARN'); + assert(!result.success); + expect(result.error.message).toContain('Invalid agent runtime ARN'); }); it('rejects custom evaluator names in ARN mode', async () => { @@ -543,8 +545,8 @@ describe('handleRunEval', () => { days: 7, }); - expect(result.success).toBe(false); - expect(result.error).toContain('cannot be resolved in ARN mode'); + assert(!result.success); + expect(result.error.message).toContain('cannot be resolved in ARN mode'); }); it('saves to cwd in ARN mode when no --output is specified', async () => { @@ -559,7 +561,7 @@ describe('handleRunEval', () => { days: 7, }); - expect(result.success).toBe(true); + assert(result.success); // Should write to cwd, not call saveEvalRun (which requires a project) expect(mockSaveEvalRun).not.toHaveBeenCalled(); expect(mockWriteFileSync).toHaveBeenCalledWith( @@ -582,7 +584,7 @@ describe('handleRunEval', () => { output: '/tmp/custom-eval.json', }); - expect(result.success).toBe(true); + assert(result.success); expect(mockWriteFileSync).toHaveBeenCalledWith('/tmp/custom-eval.json', expect.any(String)); expect(result.filePath).toBe('/tmp/custom-eval.json'); }); @@ -594,8 +596,8 @@ describe('handleRunEval', () => { days: 7, }); - expect(result.success).toBe(false); - expect(result.error).toContain('No evaluators specified'); + assert(!result.success); + expect(result.error.message).toContain('No evaluators specified'); }); // ─── Endpoint selection ────────────────────────────────────────────────── @@ -1199,8 +1201,8 @@ describe('handleRunEval', () => { expectedTrajectory: ['tool_1', 'tool_2'], }); - expect(result.success).toBe(true); - expect(result.run!.referenceInputs).toEqual({ + assert(result.success); + expect(result.run.referenceInputs).toEqual({ expectedTrajectory: ['tool_1', 'tool_2'], }); }); @@ -1215,7 +1217,7 @@ describe('handleRunEval', () => { assertions: ['Agent should greet user'], }); - expect(result.success).toBe(false); - expect(result.error).toContain('require exactly one session'); + assert(!result.success); + expect(result.error.message).toContain('require exactly one session'); }); }); diff --git a/src/cli/operations/eval/index.ts b/src/cli/operations/eval/index.ts index de5999569..66efbbb21 100644 --- a/src/cli/operations/eval/index.ts +++ b/src/cli/operations/eval/index.ts @@ -5,6 +5,5 @@ export type { ListEvalRunsResult } from './list-eval-runs'; export { handlePauseResume } from './pause-resume'; export type { PauseResumeResult } from './pause-resume'; export { handleLogsEval } from './logs-eval'; -export type { LogsEvalResult } from './logs-eval'; export type { EvalRunResult, RunEvalOptions, ListEvalRunsOptions, OnlineEvalActionOptions, SessionInfo } from './types'; export type { LogsEvalOptions } from './logs-eval'; diff --git a/src/cli/operations/eval/list-eval-runs.ts b/src/cli/operations/eval/list-eval-runs.ts index 66b0ed528..9b79a9ebc 100644 --- a/src/cli/operations/eval/list-eval-runs.ts +++ b/src/cli/operations/eval/list-eval-runs.ts @@ -1,12 +1,9 @@ -import { getErrorMessage } from '../../errors'; +import { toError } from '../../../lib'; +import type { Result } from '../../../lib/result'; import { listEvalRuns } from './storage'; import type { EvalRunResult, ListEvalRunsOptions } from './types'; -export interface ListEvalRunsResult { - success: boolean; - error?: string; - runs?: EvalRunResult[]; -} +export type ListEvalRunsResult = Result<{ runs: EvalRunResult[] }>; export function handleListEvalRuns(options: ListEvalRunsOptions): ListEvalRunsResult { try { @@ -22,6 +19,6 @@ export function handleListEvalRuns(options: ListEvalRunsOptions): ListEvalRunsRe return { success: true, runs }; } catch (err) { - return { success: false, error: getErrorMessage(err) }; + return { success: false, error: toError(err) }; } } diff --git a/src/cli/operations/eval/logs-eval.ts b/src/cli/operations/eval/logs-eval.ts index 430103a44..70df24eb4 100644 --- a/src/cli/operations/eval/logs-eval.ts +++ b/src/cli/operations/eval/logs-eval.ts @@ -1,3 +1,5 @@ +import { ResourceNotFoundError, ValidationError } from '../../../lib'; +import type { Result } from '../../../lib/result'; import { parseTimeString } from '../../../lib/utils'; import { getOnlineEvaluationConfig } from '../../aws/agentcore-control'; import { searchLogs, streamLogs } from '../../aws/cloudwatch'; @@ -13,11 +15,6 @@ export interface LogsEvalOptions { follow?: boolean; } -export interface LogsEvalResult { - success: boolean; - error?: string; -} - function formatLogLine(event: { timestamp: number; message: string }, json: boolean): string { if (json) { return JSON.stringify({ timestamp: new Date(event.timestamp).toISOString(), message: event.message }); @@ -70,12 +67,12 @@ async function resolveEvalLogGroups( return results; } -export async function handleLogsEval(options: LogsEvalOptions): Promise { +export async function handleLogsEval(options: LogsEvalOptions): Promise { const context = await loadDeployedProjectConfig(); const agentResult = resolveAgent(context, { runtime: options.agent }); if (!agentResult.success) { - return { success: false, error: agentResult.error }; + return { success: false, error: new ResourceNotFoundError(agentResult.error) }; } const { agent } = agentResult; @@ -85,7 +82,9 @@ export async function handleLogsEval(options: LogsEvalOptions): Promise; async function resolveOnlineEvalConfig( configName: string @@ -88,7 +85,7 @@ export async function handlePauseResume( ): Promise { const resolution = await resolveConfig(options); if (!resolution.success) { - return resolution; + return { success: false, error: new ResourceNotFoundError(resolution.error) }; } const executionStatus: OnlineEvalExecutionStatus = action === 'pause' ? 'DISABLED' : 'ENABLED'; @@ -106,6 +103,6 @@ export async function handlePauseResume( executionStatus: result.executionStatus, }; } catch (err) { - return { success: false, error: (err as Error).message }; + return { success: false, error: toError(err) }; } } diff --git a/src/cli/operations/eval/run-batch-evaluation.ts b/src/cli/operations/eval/run-batch-evaluation.ts index 0962f4e0a..41b538771 100644 --- a/src/cli/operations/eval/run-batch-evaluation.ts +++ b/src/cli/operations/eval/run-batch-evaluation.ts @@ -6,7 +6,12 @@ * 4. Poll GetBatchEvaluation until terminal status * 5. Return results */ +<<<<<<< HEAD import { ConfigIO } from '../../../lib'; +======= +import { ConfigIO, ResourceNotFoundError, ValidationError, toError } from '../../../lib'; +import type { Result } from '../../../lib/result'; +>>>>>>> origin/main import type { DeployedState } from '../../../schema'; import { generateClientToken, getBatchEvaluation, startBatchEvaluation } from '../../aws/agentcore-batch-evaluation'; import type { @@ -54,9 +59,13 @@ export interface BatchEvaluationResult { error?: string; } +<<<<<<< HEAD export interface RunBatchEvaluationCommandResult { success: boolean; error?: string; +======= +export type RunBatchEvaluationCommandResult = Result & { +>>>>>>> origin/main batchEvaluationId?: string; name?: string; status?: string; @@ -65,7 +74,11 @@ export interface RunBatchEvaluationCommandResult { startedAt?: string; completedAt?: string; logFilePath?: string; +<<<<<<< HEAD } +======= +}; +>>>>>>> origin/main // ============================================================================ // Constants @@ -116,7 +129,11 @@ export async function runBatchEvaluationCommand( logger?.log(error, 'error'); logger?.endStep('error', error); logger?.finalize(false); +<<<<<<< HEAD return { success: false, error, results: [], logFilePath: logger?.logFilePath }; +======= + return { success: false, error: new ResourceNotFoundError(error), results: [], logFilePath: logger?.logFilePath }; +>>>>>>> origin/main } const runtimeId = agentState.runtimeId; @@ -152,7 +169,13 @@ export async function runBatchEvaluationCommand( if (!/^[a-zA-Z][a-zA-Z0-9_]{0,47}$/.test(options.name)) { return { success: false, +<<<<<<< HEAD error: `Batch evaluation name must start with a letter and contain only letters, digits, and underscores (max 48 chars). Got: "${options.name}"`, +======= + error: new ValidationError( + `Batch evaluation name must start with a letter and contain only letters, digits, and underscores (max 48 chars). Got: "${options.name}"` + ), +>>>>>>> origin/main results: [], logFilePath: logger?.logFilePath, }; @@ -242,7 +265,11 @@ export async function runBatchEvaluationCommand( logger?.finalize(false); return { success: false, +<<<<<<< HEAD error, +======= + error: new Error(error), +>>>>>>> origin/main batchEvaluationId: startResult.batchEvaluationId, name: evalName, status: current.status, @@ -287,7 +314,11 @@ export async function runBatchEvaluationCommand( const error = err instanceof Error ? err.message : String(err); logger?.log(error, 'error'); logger?.finalize(false); +<<<<<<< HEAD return { success: false, error, results: [], logFilePath: logger?.logFilePath }; +======= + return { success: false, error: toError(err), results: [], logFilePath: logger?.logFilePath }; +>>>>>>> origin/main } } diff --git a/src/cli/operations/eval/run-eval.ts b/src/cli/operations/eval/run-eval.ts index 90cd519c7..8ba5cb307 100644 --- a/src/cli/operations/eval/run-eval.ts +++ b/src/cli/operations/eval/run-eval.ts @@ -1,3 +1,5 @@ +import { ResourceNotFoundError, ValidationError } from '../../../lib'; +import type { Result } from '../../../lib/result'; import { getCredentialProvider } from '../../aws'; import { evaluate } from '../../aws/agentcore'; import type { EvaluationReferenceInput } from '../../aws/agentcore'; @@ -551,12 +553,7 @@ async function fetchSessionSpans(opts: FetchSpansOptions): Promise; export async function handleRunEval(options: RunEvalOptions): Promise { let resolution: ResolveResult; @@ -569,7 +566,7 @@ export async function handleRunEval(options: RunEvalOptions): Promise ({ configExists = mockConfigExists; }, requireConfigRoot: () => '/project/agentcore', + findConfigRoot: () => '/project/agentcore', + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), + serializeResult: (r: unknown) => r, + ResourceNotFoundError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ResourceNotFoundError'; + } + }, + APP_DIR: 'app', + MCP_APP_SUBDIR: 'mcp', })); const computeDefaultGatewayEnvVarName = (name: string) => GatewayPrimitive.computeDefaultGatewayEnvVarName(name); @@ -200,7 +211,10 @@ describe('GatewayPrimitive.add (createGateway)', () => { }); expect(result).toEqual( - expect.objectContaining({ success: false, error: expect.stringContaining('Gateway "dup-gw" already exists') }) + expect.objectContaining({ + success: false, + error: expect.objectContaining({ message: expect.stringContaining('Gateway "dup-gw" already exists') }), + }) ); }); diff --git a/src/cli/operations/memory/__tests__/create-memory.test.ts b/src/cli/operations/memory/__tests__/create-memory.test.ts index a0b8077c4..ad9cef2c8 100644 --- a/src/cli/operations/memory/__tests__/create-memory.test.ts +++ b/src/cli/operations/memory/__tests__/create-memory.test.ts @@ -15,6 +15,15 @@ vi.mock('../../../../lib/index.js', () => ({ readProjectSpec = mockReadProjectSpec; writeProjectSpec = mockWriteProjectSpec; }, + findConfigRoot: () => '/fake/root', + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), + serializeResult: (r: unknown) => r, + ResourceNotFoundError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ResourceNotFoundError'; + } + }, })); const makeProject = (memoryNames: string[]) => ({ @@ -86,7 +95,7 @@ describe('add', () => { expiry: 30, }); - expect(result).toEqual(expect.objectContaining({ success: false, error: expect.any(String) })); + expect(result).toEqual(expect.objectContaining({ success: false, error: expect.any(Error) })); expect(mockWriteProjectSpec).not.toHaveBeenCalled(); }); @@ -96,7 +105,10 @@ describe('add', () => { const result = await primitive.add({ name: 'Existing', strategies: '', expiry: 30 }); expect(result).toEqual( - expect.objectContaining({ success: false, error: expect.stringContaining('Memory "Existing" already exists') }) + expect.objectContaining({ + success: false, + error: expect.objectContaining({ message: expect.stringContaining('Memory "Existing" already exists') }), + }) ); }); }); diff --git a/src/cli/operations/memory/list-memory-records.ts b/src/cli/operations/memory/list-memory-records.ts index 5d0c53828..a30a1cfab 100644 --- a/src/cli/operations/memory/list-memory-records.ts +++ b/src/cli/operations/memory/list-memory-records.ts @@ -1,5 +1,12 @@ +<<<<<<< HEAD import { createAgentCoreClient } from '../../aws'; import { ListMemoryRecordsCommand } from '@aws-sdk/client-bedrock-agentcore'; +======= +import { ResourceNotFoundError, toError } from '../../../lib'; +import type { Result } from '../../../lib/result'; +import { getCredentialProvider } from '../../aws'; +import { BedrockAgentCoreClient, ListMemoryRecordsCommand } from '@aws-sdk/client-bedrock-agentcore'; +>>>>>>> origin/main export interface MemoryRecordEntry { memoryRecordId: string; @@ -20,12 +27,7 @@ export interface ListMemoryRecordsOptions { nextToken?: string; } -export interface ListMemoryRecordsResult { - success: boolean; - records?: MemoryRecordEntry[]; - nextToken?: string; - error?: string; -} +export type ListMemoryRecordsResult = Result<{ records: MemoryRecordEntry[]; nextToken?: string }>; /** * Lists memory records for a deployed memory resource via the AWS SDK. @@ -60,8 +62,11 @@ export async function listMemoryRecords(options: ListMemoryRecordsOptions): Prom } catch (error: unknown) { const err = error as Error; if (err.name === 'ResourceNotFoundException') { - return { success: false, error: `Memory '${memoryId}' not found. It may not have been deployed yet.` }; + return { + success: false, + error: new ResourceNotFoundError(`Memory '${memoryId}' not found. It may not have been deployed yet.`), + }; } - return { success: false, error: err.message ?? String(error) }; + return { success: false, error: toError(error) }; } } diff --git a/src/cli/operations/memory/retrieve-memory-records.ts b/src/cli/operations/memory/retrieve-memory-records.ts index 720a45287..071b68bf4 100644 --- a/src/cli/operations/memory/retrieve-memory-records.ts +++ b/src/cli/operations/memory/retrieve-memory-records.ts @@ -1,4 +1,10 @@ +<<<<<<< HEAD import { createAgentCoreClient } from '../../aws'; +======= +import { ResourceNotFoundError, toError } from '../../../lib'; +import type { Result } from '../../../lib/result'; +import { getCredentialProvider } from '../../aws'; +>>>>>>> origin/main import type { MemoryRecordEntry } from './list-memory-records'; import { RetrieveMemoryRecordsCommand } from '@aws-sdk/client-bedrock-agentcore'; @@ -13,12 +19,7 @@ export interface RetrieveMemoryRecordsOptions { nextToken?: string; } -export interface RetrieveMemoryRecordsResult { - success: boolean; - records?: MemoryRecordEntry[]; - nextToken?: string; - error?: string; -} +export type RetrieveMemoryRecordsResult = Result<{ records: MemoryRecordEntry[]; nextToken?: string }>; /** * Searches memory records using semantic retrieval via the AWS SDK. @@ -59,8 +60,11 @@ export async function retrieveMemoryRecords( } catch (error: unknown) { const err = error as Error; if (err.name === 'ResourceNotFoundException') { - return { success: false, error: `Memory '${memoryId}' not found. It may not have been deployed yet.` }; + return { + success: false, + error: new ResourceNotFoundError(`Memory '${memoryId}' not found. It may not have been deployed yet.`), + }; } - return { success: false, error: err.message ?? String(error) }; + return { success: false, error: toError(error) }; } } diff --git a/src/cli/operations/recommendation/__tests__/apply-to-bundle.test.ts b/src/cli/operations/recommendation/__tests__/apply-to-bundle.test.ts index 5e0fb668a..ef9c298c2 100644 --- a/src/cli/operations/recommendation/__tests__/apply-to-bundle.test.ts +++ b/src/cli/operations/recommendation/__tests__/apply-to-bundle.test.ts @@ -1,6 +1,10 @@ import type { ConfigIO } from '../../../../lib'; import type { RecommendationResult } from '../../../aws/agentcore-recommendation'; import { applyRecommendationToBundle } from '../apply-to-bundle'; +<<<<<<< HEAD +======= +import assert from 'node:assert'; +>>>>>>> origin/main import { describe, expect, it, vi } from 'vitest'; const { RUNTIME_ARN, BUNDLE_ARN, NEW_VERSION_ID } = vi.hoisted(() => ({ @@ -98,7 +102,11 @@ describe('applyRecommendationToBundle', () => { configIO ); +<<<<<<< HEAD expect(applyResult.success).toBe(true); +======= + assert(applyResult.success); +>>>>>>> origin/main expect(applyResult.newVersionId).toBe(NEW_VERSION_ID); // Verify spec was written with server components @@ -132,7 +140,11 @@ describe('applyRecommendationToBundle', () => { configIO ); +<<<<<<< HEAD expect(applyResult.success).toBe(true); +======= + assert(applyResult.success); +>>>>>>> origin/main expect(applyResult.newVersionId).toBe(NEW_VERSION_ID); }); @@ -152,7 +164,11 @@ describe('applyRecommendationToBundle', () => { configIO ); +<<<<<<< HEAD expect(applyResult.success).toBe(true); +======= + assert(applyResult.success); +>>>>>>> origin/main expect(applyResult.newVersionId).toBe(NEW_VERSION_ID); }); @@ -171,8 +187,13 @@ describe('applyRecommendationToBundle', () => { configIO ); +<<<<<<< HEAD expect(applyResult.success).toBe(false); expect(applyResult.error).toContain('does not contain a new config bundle version'); +======= + assert(!applyResult.success); + expect(applyResult.error.message).toContain('does not contain a new config bundle version'); +>>>>>>> origin/main expect(writeSpecSpy).not.toHaveBeenCalled(); }); @@ -192,8 +213,13 @@ describe('applyRecommendationToBundle', () => { configIO ); +<<<<<<< HEAD expect(applyResult.success).toBe(false); expect(applyResult.error).toContain('NonExistent'); +======= + assert(!applyResult.success); + expect(applyResult.error.message).toContain('NonExistent'); +>>>>>>> origin/main expect(writeSpecSpy).not.toHaveBeenCalled(); }); }); diff --git a/src/cli/operations/recommendation/__tests__/recommendation-storage.test.ts b/src/cli/operations/recommendation/__tests__/recommendation-storage.test.ts index f6a60b6e8..f20b0bab5 100644 --- a/src/cli/operations/recommendation/__tests__/recommendation-storage.test.ts +++ b/src/cli/operations/recommendation/__tests__/recommendation-storage.test.ts @@ -17,7 +17,13 @@ function makeTmpDir(): string { return dir; } +<<<<<<< HEAD function makeResult(overrides: Partial = {}): RunRecommendationCommandResult { +======= +function makeResult( + overrides: Partial> = {} +): RunRecommendationCommandResult { +>>>>>>> origin/main return { success: true, recommendationId: 'rec-123', diff --git a/src/cli/operations/recommendation/__tests__/run-recommendation.test.ts b/src/cli/operations/recommendation/__tests__/run-recommendation.test.ts index b26a59b32..10dafad42 100644 --- a/src/cli/operations/recommendation/__tests__/run-recommendation.test.ts +++ b/src/cli/operations/recommendation/__tests__/run-recommendation.test.ts @@ -1,4 +1,8 @@ import { runRecommendationCommand } from '../run-recommendation'; +<<<<<<< HEAD +======= +import assert from 'node:assert'; +>>>>>>> origin/main import { beforeEach, describe, expect, it, vi } from 'vitest'; // Mock dependencies — paths are relative to the file under test (run-recommendation.ts) @@ -29,6 +33,28 @@ vi.mock('../../../../lib', () => ({ readDeployedState = mockReadDeployedState; resolveAWSDeploymentTargets = vi.fn().mockResolvedValue([{ region: 'us-east-1' }]); }, +<<<<<<< HEAD +======= + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), + ResourceNotFoundError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ResourceNotFoundError'; + } + }, + ValidationError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ValidationError'; + } + }, + TimeoutError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'TimeoutError'; + } + }, +>>>>>>> origin/main })); vi.mock('../../../aws/region', () => ({ @@ -71,9 +97,15 @@ describe('runRecommendationCommand', () => { traceSource: 'cloudwatch', }); +<<<<<<< HEAD expect(result.success).toBe(false); expect(result.error).toContain('NonExistentAgent'); expect(result.error).toContain('not deployed'); +======= + assert(!result.success); + expect(result.error.message).toContain('NonExistentAgent'); + expect(result.error.message).toContain('not deployed'); +>>>>>>> origin/main }); it('returns error when evaluator cannot be resolved', async () => { @@ -86,9 +118,15 @@ describe('runRecommendationCommand', () => { traceSource: 'cloudwatch', }); +<<<<<<< HEAD expect(result.success).toBe(false); expect(result.error).toContain('UnknownEvaluator'); expect(result.error).toContain('not found'); +======= + assert(!result.success); + expect(result.error.message).toContain('UnknownEvaluator'); + expect(result.error.message).toContain('not found'); +>>>>>>> origin/main }); it('returns result on COMPLETED status', async () => { @@ -123,7 +161,11 @@ describe('runRecommendationCommand', () => { pollIntervalMs: 0, }); +<<<<<<< HEAD expect(result.success).toBe(true); +======= + assert(result.success); +>>>>>>> origin/main expect(result.recommendationId).toBe('rec-001'); expect(result.status).toBe('COMPLETED'); expect(result.result?.systemPromptRecommendationResult?.recommendedSystemPrompt).toBe('Optimized prompt'); @@ -153,8 +195,13 @@ describe('runRecommendationCommand', () => { pollIntervalMs: 0, }); +<<<<<<< HEAD expect(result.success).toBe(false); expect(result.error).toContain('FAILED'); +======= + assert(!result.success); + expect(result.error.message).toContain('FAILED'); +>>>>>>> origin/main expect(result.recommendationId).toBe('rec-002'); }); @@ -287,8 +334,13 @@ describe('runRecommendationCommand', () => { traceSource: 'cloudwatch', }); +<<<<<<< HEAD expect(result.success).toBe(false); expect(result.error).toContain('API timeout'); +======= + assert(!result.success); + expect(result.error.message).toContain('API timeout'); +>>>>>>> origin/main }); it('retries transient poll failures and succeeds', async () => { @@ -345,10 +397,17 @@ describe('runRecommendationCommand', () => { pollIntervalMs: 0, }); +<<<<<<< HEAD expect(result.success).toBe(false); expect(result.error).toContain('consecutive errors'); expect(result.error).toContain('fetch failed'); expect(result.error).toContain('rec-retry-fail'); +======= + assert(!result.success); + expect(result.error.message).toContain('consecutive errors'); + expect(result.error.message).toContain('fetch failed'); + expect(result.error.message).toContain('rec-retry-fail'); +>>>>>>> origin/main expect(mockGetRecommendation).toHaveBeenCalledTimes(3); }); @@ -377,9 +436,15 @@ describe('runRecommendationCommand', () => { maxPollDurationMs: 0, // Immediately timeout }); +<<<<<<< HEAD expect(result.success).toBe(false); expect(result.error).toContain('Polling timed out'); expect(result.error).toContain('rec-timeout'); +======= + assert(!result.success); + expect(result.error.message).toContain('Polling timed out'); + expect(result.error.message).toContain('rec-timeout'); +>>>>>>> origin/main }); it('reads system prompt from file when inputSource is file', async () => { @@ -538,8 +603,13 @@ describe('runRecommendationCommand', () => { pollIntervalMs: 0, }); +<<<<<<< HEAD expect(result.success).toBe(false); expect(result.error).toContain('No spans found'); +======= + assert(!result.success); + expect(result.error.message).toContain('No spans found'); +>>>>>>> origin/main }); it('derives service name from runtimeId by stripping hash suffix', async () => { @@ -664,10 +734,17 @@ describe('runRecommendationCommand', () => { pollIntervalMs: 0, }); +<<<<<<< HEAD expect(result.success).toBe(false); expect(result.error).toContain('Insufficient trace data'); expect(result.error).toContain('INSUFFICIENT_DATA'); expect(result.error).toContain('Not enough traces'); +======= + assert(!result.success); + expect(result.error.message).toContain('Insufficient trace data'); + expect(result.error.message).toContain('INSUFFICIENT_DATA'); + expect(result.error.message).toContain('Not enough traces'); +>>>>>>> origin/main // Request IDs are logged to file only, not included in the error message }); diff --git a/src/cli/operations/recommendation/apply-to-bundle.ts b/src/cli/operations/recommendation/apply-to-bundle.ts index bf9060d10..96a456088 100644 --- a/src/cli/operations/recommendation/apply-to-bundle.ts +++ b/src/cli/operations/recommendation/apply-to-bundle.ts @@ -9,7 +9,12 @@ * This module fetches that new version via GetConfigurationBundleVersion and * updates the local agentcore.json components to match the server state. */ +<<<<<<< HEAD import { ConfigIO } from '../../../lib'; +======= +import { ConfigIO, ResourceNotFoundError } from '../../../lib'; +import type { Result } from '../../../lib/result'; +>>>>>>> origin/main import { getConfigurationBundleVersion } from '../../aws/agentcore-config-bundles'; import type { RecommendationResult } from '../../aws/agentcore-recommendation'; @@ -24,12 +29,16 @@ export interface ApplyRecommendationOptions { region: string; } +<<<<<<< HEAD export interface ApplyRecommendationResult { success: boolean; error?: string; /** New version ID that was synced from the server */ newVersionId?: string; } +======= +export type ApplyRecommendationResult = Result<{ newVersionId?: string }>; +>>>>>>> origin/main /** * Extract the bundleId from a bundle ARN. @@ -58,8 +67,14 @@ export async function applyRecommendationToBundle( if (!resultBundle) { return { success: false, +<<<<<<< HEAD error: 'Recommendation result does not contain a new config bundle version. The server may not have applied the recommendation to the bundle.', +======= + error: new Error( + 'Recommendation result does not contain a new config bundle version. The server may not have applied the recommendation to the bundle.' + ), +>>>>>>> origin/main }; } @@ -67,7 +82,11 @@ export async function applyRecommendationToBundle( if (!bundleId) { return { success: false, +<<<<<<< HEAD error: `Could not extract bundle ID from ARN: ${resultBundle.bundleArn}`, +======= + error: new Error(`Could not extract bundle ID from ARN: ${resultBundle.bundleArn}`), +>>>>>>> origin/main }; } @@ -107,7 +126,11 @@ export async function applyRecommendationToBundle( if (!bundle) { return { success: false, +<<<<<<< HEAD error: `Config bundle "${identifier}" not found in agentcore.json.`, +======= + error: new ResourceNotFoundError(`Config bundle "${identifier}" not found in agentcore.json.`), +>>>>>>> origin/main }; } diff --git a/src/cli/operations/recommendation/recommendation-storage.ts b/src/cli/operations/recommendation/recommendation-storage.ts index ad8aa7160..2280ee21a 100644 --- a/src/cli/operations/recommendation/recommendation-storage.ts +++ b/src/cli/operations/recommendation/recommendation-storage.ts @@ -43,9 +43,15 @@ export function saveRecommendationRun( agent, evaluators, status: result.status ?? 'unknown', +<<<<<<< HEAD startedAt: result.startedAt, completedAt: result.completedAt, result: result.result, +======= + startedAt: result.success ? result.startedAt : undefined, + completedAt: result.success ? result.completedAt : undefined, + result: result.success ? result.result : undefined, +>>>>>>> origin/main }; writeFileSync(filePath, JSON.stringify(record, null, 2)); diff --git a/src/cli/operations/recommendation/run-recommendation.ts b/src/cli/operations/recommendation/run-recommendation.ts index 0423cfe32..75f4826aa 100644 --- a/src/cli/operations/recommendation/run-recommendation.ts +++ b/src/cli/operations/recommendation/run-recommendation.ts @@ -6,7 +6,11 @@ * 4. Poll GetRecommendation until terminal status * 5. Return result with optimized artifact */ +<<<<<<< HEAD import { ConfigIO } from '../../../lib'; +======= +import { ConfigIO, ResourceNotFoundError, TimeoutError, ValidationError, toError } from '../../../lib'; +>>>>>>> origin/main import type { DeployedState } from '../../../schema'; import type { RecommendationConfig, @@ -60,7 +64,11 @@ export async function runRecommendationCommand( logger?.finalize(false); return { success: false, +<<<<<<< HEAD error: `Agent "${options.agent}" not deployed. Run \`agentcore deploy\` first.`, +======= + error: new Error(`Agent "${options.agent}" not deployed. Run \`agentcore deploy\` first.`), +>>>>>>> origin/main logFilePath: logger?.logFilePath, }; } @@ -73,7 +81,13 @@ export async function runRecommendationCommand( if (!evaluatorId) { return { success: false, +<<<<<<< HEAD error: `Evaluator "${evaluator}" not found in deployed state. Use a Builtin.* name, a full ARN, or deploy a custom evaluator first.`, +======= + error: new Error( + `Evaluator "${evaluator}" not found in deployed state. Use a Builtin.* name, a full ARN, or deploy a custom evaluator first.` + ), +>>>>>>> origin/main logFilePath: logger?.logFilePath, }; } @@ -82,7 +96,11 @@ export async function runRecommendationCommand( if (options.type === 'SYSTEM_PROMPT_RECOMMENDATION' && evaluatorIds.length !== 1) { return { success: false, +<<<<<<< HEAD error: 'System prompt recommendations require exactly one evaluator.', +======= + error: new ValidationError('System prompt recommendations require exactly one evaluator.'), +>>>>>>> origin/main logFilePath: logger?.logFilePath, }; } @@ -105,7 +123,13 @@ export async function runRecommendationCommand( ) { return { success: false, +<<<<<<< HEAD error: 'System prompt content is required. Provide via --inline, --prompt-file, or --bundle-name.', +======= + error: new ValidationError( + 'System prompt content is required. Provide via --inline, --prompt-file, or --bundle-name.' + ), +>>>>>>> origin/main logFilePath: logger?.logFilePath, }; } @@ -132,7 +156,13 @@ export async function runRecommendationCommand( if (!bundleArn) { return { success: false, +<<<<<<< HEAD error: `Config bundle "${options.bundleName}" not found in deployed state. Run \`agentcore deploy\` first.`, +======= + error: new ResourceNotFoundError( + `Config bundle "${options.bundleName}" not found in deployed state. Run \`agentcore deploy\` first.` + ), +>>>>>>> origin/main logFilePath: logger?.logFilePath, }; } @@ -230,7 +260,13 @@ export async function runRecommendationCommand( logger?.finalize(false); return { success: false, +<<<<<<< HEAD error: `Polling timed out after ${Math.round(maxDurationMs / 60000)} minutes. The recommendation may still be running server-side.\nRecommendation ID: ${startResult.recommendationId}`, +======= + error: new TimeoutError( + `Polling timed out after ${Math.round(maxDurationMs / 60000)} minutes. The recommendation may still be running server-side.\nRecommendation ID: ${startResult.recommendationId}` + ), +>>>>>>> origin/main recommendationId: startResult.recommendationId, status: currentStatus, logFilePath: logger?.logFilePath, @@ -255,7 +291,13 @@ export async function runRecommendationCommand( logger?.finalize(false); return { success: false, +<<<<<<< HEAD error: `Polling failed after ${MAX_POLL_RETRIES} consecutive errors: ${pollErrMsg}\nThe recommendation may still be running server-side.\nRecommendation ID: ${startResult.recommendationId}`, +======= + error: new TimeoutError( + `Polling failed after ${MAX_POLL_RETRIES} consecutive errors: ${pollErrMsg}\nThe recommendation may still be running server-side.\nRecommendation ID: ${startResult.recommendationId}` + ), +>>>>>>> origin/main recommendationId: startResult.recommendationId, status: currentStatus, logFilePath: logger?.logFilePath, @@ -303,9 +345,17 @@ export async function runRecommendationCommand( return { success: false, +<<<<<<< HEAD error: failureDetails ? `Recommendation failed: ${failureDetails}` : `Recommendation finished with status: ${currentStatus}`, +======= + error: new Error( + failureDetails + ? `Recommendation failed: ${failureDetails}` + : `Recommendation finished with status: ${currentStatus}` + ), +>>>>>>> origin/main recommendationId: startResult.recommendationId, status: currentStatus, logFilePath: logger?.logFilePath, @@ -319,7 +369,11 @@ export async function runRecommendationCommand( logger?.finalize(false); return { success: false, +<<<<<<< HEAD error: `Recommendation ended with unexpected status: ${currentStatus}`, +======= + error: new Error(`Recommendation ended with unexpected status: ${currentStatus}`), +>>>>>>> origin/main recommendationId: startResult.recommendationId, status: currentStatus, logFilePath: logger?.logFilePath, @@ -331,7 +385,11 @@ export async function runRecommendationCommand( logger?.finalize(false); return { success: false, +<<<<<<< HEAD error: errorMsg, +======= + error: toError(err), +>>>>>>> origin/main logFilePath: logger?.logFilePath, }; } diff --git a/src/cli/operations/recommendation/types.ts b/src/cli/operations/recommendation/types.ts index 426ba84a8..b2dc7fad3 100644 --- a/src/cli/operations/recommendation/types.ts +++ b/src/cli/operations/recommendation/types.ts @@ -1,6 +1,10 @@ /** * Shared types for the recommendation feature. */ +<<<<<<< HEAD +======= +import type { Result } from '../../../lib/result'; +>>>>>>> origin/main import type { RecommendationResult, RecommendationType } from '../../aws/agentcore-recommendation'; export type { RecommendationType } from '../../aws/agentcore-recommendation'; @@ -56,6 +60,7 @@ export interface RunRecommendationCommandOptions { onStarted?: (info: { recommendationId: string; region: string }) => void; } +<<<<<<< HEAD export interface RunRecommendationCommandResult { success: boolean; error?: string; @@ -70,3 +75,11 @@ export interface RunRecommendationCommandResult { /** Path to the execution log file */ logFilePath?: string; } +======= +export type RunRecommendationCommandResult = Result<{ + result?: RecommendationResult; + region?: string; + startedAt?: string; + completedAt?: string; +}> & { recommendationId?: string; status?: string; logFilePath?: string }; +>>>>>>> origin/main diff --git a/src/cli/operations/remove/__tests__/remove-agent-ops.test.ts b/src/cli/operations/remove/__tests__/remove-agent-ops.test.ts index fb2b9fedc..ece57dcb9 100644 --- a/src/cli/operations/remove/__tests__/remove-agent-ops.test.ts +++ b/src/cli/operations/remove/__tests__/remove-agent-ops.test.ts @@ -1,3 +1,4 @@ +import { ResourceNotFoundError } from '../../../../lib'; import { AgentPrimitive } from '../../../primitives/AgentPrimitive.js'; import { afterEach, describe, expect, it, vi } from 'vitest'; @@ -15,6 +16,29 @@ vi.mock('../../../../lib/index.js', () => ({ readProjectSpec = mockReadProjectSpec; writeProjectSpec = mockWriteProjectSpec; }, + findConfigRoot: () => '/fake/root', + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), + serializeResult: (r: unknown) => r, + ResourceNotFoundError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ResourceNotFoundError'; + } + }, + ConflictError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ConflictError'; + } + }, + NoProjectError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'NoProjectError'; + } + }, + APP_DIR: 'app', + setEnvVar: vi.fn().mockResolvedValue(undefined), })); const makeProject = (agentNames: string[]) => ({ @@ -87,7 +111,7 @@ describe('remove', () => { const result = await primitive.remove('Missing'); - expect(result).toEqual({ success: false, error: 'Agent "Missing" not found.' }); + expect(result).toEqual({ success: false, error: new ResourceNotFoundError('Agent "Missing" not found.') }); }); it('returns error on exception', async () => { @@ -95,6 +119,6 @@ describe('remove', () => { const result = await primitive.remove('Agent1'); - expect(result).toEqual({ success: false, error: 'read fail' }); + expect(result).toEqual({ success: false, error: new Error('read fail') }); }); }); diff --git a/src/cli/operations/remove/__tests__/remove-gateway-ops.test.ts b/src/cli/operations/remove/__tests__/remove-gateway-ops.test.ts index 54293df5a..d44a4bb3c 100644 --- a/src/cli/operations/remove/__tests__/remove-gateway-ops.test.ts +++ b/src/cli/operations/remove/__tests__/remove-gateway-ops.test.ts @@ -1,3 +1,4 @@ +import { ResourceNotFoundError } from '../../../../lib'; import { GatewayPrimitive } from '../../../primitives/GatewayPrimitive.js'; import { afterEach, describe, expect, it, vi } from 'vitest'; @@ -11,6 +12,15 @@ vi.mock('../../../../lib/index.js', () => ({ writeProjectSpec = mockWriteProjectSpec; configExists = mockConfigExists; }, + findConfigRoot: () => '/fake/root', + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), + serializeResult: (r: unknown) => r, + ResourceNotFoundError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ResourceNotFoundError'; + } + }, })); const makeMcpSpec = (gatewayNames: string[], targetsPerGateway = 0) => ({ @@ -101,7 +111,7 @@ describe('remove', () => { const result = await primitive.remove('missing'); - expect(result).toEqual({ success: false, error: 'Gateway "missing" not found.' }); + expect(result).toEqual({ success: false, error: new ResourceNotFoundError('Gateway "missing" not found.') }); }); it('returns error on exception', async () => { @@ -109,6 +119,6 @@ describe('remove', () => { const result = await primitive.remove('gw1'); - expect(result).toEqual({ success: false, error: 'read fail' }); + expect(result).toEqual({ success: false, error: new Error('read fail') }); }); }); diff --git a/src/cli/operations/remove/__tests__/remove-identity-ops.test.ts b/src/cli/operations/remove/__tests__/remove-identity-ops.test.ts index 1b1bbb8e7..f34cee7f3 100644 --- a/src/cli/operations/remove/__tests__/remove-identity-ops.test.ts +++ b/src/cli/operations/remove/__tests__/remove-identity-ops.test.ts @@ -1,3 +1,4 @@ +import { ResourceNotFoundError } from '../../../../lib'; import { CredentialPrimitive } from '../../../primitives/CredentialPrimitive.js'; import { afterEach, describe, expect, it, vi } from 'vitest'; @@ -16,6 +17,23 @@ vi.mock('../../../../lib/index.js', () => ({ writeProjectSpec = mockWriteProjectSpec; configExists = vi.fn().mockReturnValue(false); }, + findConfigRoot: () => '/fake/root', + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), + serializeResult: (r: unknown) => r, + ResourceNotFoundError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ResourceNotFoundError'; + } + }, + ConflictError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ConflictError'; + } + }, + setEnvVar: vi.fn().mockResolvedValue(undefined), + getEnvVar: vi.fn(), })); const makeProject = ( @@ -93,7 +111,7 @@ describe('remove', () => { const result = await primitive.remove('Missing'); - expect(result).toEqual({ success: false, error: 'Credential "Missing" not found.' }); + expect(result).toEqual({ success: false, error: new ResourceNotFoundError('Credential "Missing" not found.') }); }); it('returns error on exception', async () => { @@ -101,6 +119,6 @@ describe('remove', () => { const result = await primitive.remove('Cred1'); - expect(result).toEqual({ success: false, error: 'read fail' }); + expect(result).toEqual({ success: false, error: new Error('read fail') }); }); }); diff --git a/src/cli/operations/remove/__tests__/remove-memory-ops.test.ts b/src/cli/operations/remove/__tests__/remove-memory-ops.test.ts index d42bedb94..29b485c15 100644 --- a/src/cli/operations/remove/__tests__/remove-memory-ops.test.ts +++ b/src/cli/operations/remove/__tests__/remove-memory-ops.test.ts @@ -1,3 +1,4 @@ +import { ResourceNotFoundError } from '../../../../lib'; import { MemoryPrimitive } from '../../../primitives/MemoryPrimitive.js'; import { afterEach, describe, expect, it, vi } from 'vitest'; @@ -15,6 +16,15 @@ vi.mock('../../../../lib/index.js', () => ({ readProjectSpec = mockReadProjectSpec; writeProjectSpec = mockWriteProjectSpec; }, + findConfigRoot: () => '/fake/root', + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), + serializeResult: (r: unknown) => r, + ResourceNotFoundError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ResourceNotFoundError'; + } + }, })); const makeProject = (memoryNames: string[]) => ({ @@ -84,7 +94,7 @@ describe('remove', () => { const result = await primitive.remove('Missing'); - expect(result).toEqual({ success: false, error: 'Memory "Missing" not found.' }); + expect(result).toEqual({ success: false, error: new ResourceNotFoundError('Memory "Missing" not found.') }); }); it('returns error on exception', async () => { @@ -92,6 +102,6 @@ describe('remove', () => { const result = await primitive.remove('Mem1'); - expect(result).toEqual({ success: false, error: 'read fail' }); + expect(result).toEqual({ success: false, error: new Error('read fail') }); }); }); diff --git a/src/cli/operations/remove/remove-gateway-target.ts b/src/cli/operations/remove/remove-gateway-target.ts index 0ceebf7eb..fa84d8b70 100644 --- a/src/cli/operations/remove/remove-gateway-target.ts +++ b/src/cli/operations/remove/remove-gateway-target.ts @@ -1,6 +1,7 @@ -import { ConfigIO } from '../../../lib'; +import { ConfigIO, ResourceNotFoundError, toError } from '../../../lib'; +import type { Result } from '../../../lib/result'; import type { AgentCoreCliMcpDefs, AgentCoreMcpSpec } from '../../../schema'; -import type { RemovalPreview, RemovalResult, SchemaChange } from './types'; +import type { RemovalPreview, SchemaChange } from './types'; import { existsSync } from 'fs'; import { rm } from 'fs/promises'; import { join } from 'path'; @@ -155,7 +156,7 @@ function computeRemovedToolMcpDefs( /** * Remove a gateway target from the project. */ -export async function removeGatewayTarget(tool: RemovableGatewayTarget): Promise { +export async function removeGatewayTarget(tool: RemovableGatewayTarget): Promise { try { const configIO = new ConfigIO(); const project = await configIO.readProjectSpec(); @@ -172,11 +173,14 @@ export async function removeGatewayTarget(tool: RemovableGatewayTarget): Promise const gateway = mcpSpec.agentCoreGateways.find(g => g.name === tool.gatewayName); if (!gateway) { - return { success: false, error: `Gateway "${tool.gatewayName}" not found.` }; + return { success: false, error: new ResourceNotFoundError(`Gateway "${tool.gatewayName}" not found.`) }; } const target = gateway.targets.find(t => t.name === tool.name); if (!target) { - return { success: false, error: `Target "${tool.name}" not found in gateway "${tool.gatewayName}".` }; + return { + success: false, + error: new ResourceNotFoundError(`Target "${tool.name}" not found in gateway "${tool.gatewayName}".`), + }; } if (target.compute?.implementation && 'path' in target.compute.implementation) { toolPath = target.compute.implementation.path; @@ -200,7 +204,6 @@ export async function removeGatewayTarget(tool: RemovableGatewayTarget): Promise return { success: true }; } catch (err) { - const message = err instanceof Error ? err.message : 'Unknown error'; - return { success: false, error: message }; + return { success: false, error: toError(err) }; } } diff --git a/src/cli/operations/remove/types.ts b/src/cli/operations/remove/types.ts index 2194a66f7..66c2af228 100644 --- a/src/cli/operations/remove/types.ts +++ b/src/cli/operations/remove/types.ts @@ -21,11 +21,6 @@ export interface RemovalPreview { schemaChanges: SchemaChange[]; } -/** - * Result of a removal operation. - */ -export type RemovalResult = { success: true } | { success: false; error: string }; - /** * Snapshot of all schemas before removal (for diff computation). */ diff --git a/src/cli/operations/traces/__tests__/get-trace.test.ts b/src/cli/operations/traces/__tests__/get-trace.test.ts index 8f833cc0a..e51107369 100644 --- a/src/cli/operations/traces/__tests__/get-trace.test.ts +++ b/src/cli/operations/traces/__tests__/get-trace.test.ts @@ -1,5 +1,9 @@ import { fetchTraceRecords, getTrace } from '../get-trace'; import type { FetchTraceRecordsOptions } from '../types'; +<<<<<<< HEAD +======= +import assert from 'node:assert'; +>>>>>>> origin/main import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; @@ -57,14 +61,14 @@ describe('fetchTraceRecords', () => { const result = await fetchTraceRecords(baseOptions); - expect(result.success).toBe(true); + assert(result.success); expect(result.records).toHaveLength(2); - expect(result.records![0]).toEqual({ + expect(result.records[0]).toEqual({ '@timestamp': '2024-01-01T00:00:00Z', '@message': { traceId: 'abc123', spanId: 'span1' }, '@ptr': 'ptr-value-1', }); - expect(result.records![1]).toEqual({ + expect(result.records[1]).toEqual({ '@timestamp': '2024-01-01T00:00:01Z', '@message': { traceId: 'abc123', spanId: 'span2' }, }); @@ -76,8 +80,8 @@ describe('fetchTraceRecords', () => { traceId: 'invalid!@#$', }); - expect(result.success).toBe(false); - expect(result.error).toContain('Invalid trace ID format'); + assert(!result.success); + expect(result.error.message).toContain('Invalid trace ID format'); expect(mockSend).not.toHaveBeenCalled(); }); @@ -89,8 +93,8 @@ describe('fetchTraceRecords', () => { const result = await fetchTraceRecords(baseOptions); - expect(result.success).toBe(false); - expect(result.error).toContain('No trace data found'); + assert(!result.success); + expect(result.error.message).toContain('No trace data found'); }); it('returns error when query fails to start', async () => { @@ -98,8 +102,8 @@ describe('fetchTraceRecords', () => { const result = await fetchTraceRecords(baseOptions); - expect(result.success).toBe(false); - expect(result.error).toContain('Failed to start CloudWatch Logs Insights query'); + assert(!result.success); + expect(result.error.message).toContain('Failed to start CloudWatch Logs Insights query'); }); it('returns error when query status is Failed', async () => { @@ -107,8 +111,8 @@ describe('fetchTraceRecords', () => { const result = await fetchTraceRecords(baseOptions); - expect(result.success).toBe(false); - expect(result.error).toContain('failed'); + assert(!result.success); + expect(result.error.message).toContain('failed'); }); it('preserves @ptr when present in CloudWatch response', async () => { @@ -125,9 +129,9 @@ describe('fetchTraceRecords', () => { const result = await fetchTraceRecords(baseOptions); - expect(result.success).toBe(true); + assert(result.success); expect(result.records).toHaveLength(1); - expect(result.records![0]!['@ptr']).toBe('cw-ptr-123'); + expect(result.records[0]!['@ptr']).toBe('cw-ptr-123'); }); it('omits @ptr when not present in CloudWatch response', async () => { @@ -143,8 +147,8 @@ describe('fetchTraceRecords', () => { const result = await fetchTraceRecords(baseOptions); - expect(result.success).toBe(true); - expect(result.records![0]).not.toHaveProperty('@ptr'); + assert(result.success); + expect(result.records[0]).not.toHaveProperty('@ptr'); }); it('handles non-JSON @message gracefully', async () => { @@ -160,9 +164,9 @@ describe('fetchTraceRecords', () => { const result = await fetchTraceRecords(baseOptions); - expect(result.success).toBe(true); + assert(result.success); expect(result.records).toHaveLength(1); - expect(result.records![0]!['@message']).toBe('plain text message'); + expect(result.records[0]!['@message']).toBe('plain text message'); }); it('handles ResourceNotFoundException', async () => { @@ -172,9 +176,9 @@ describe('fetchTraceRecords', () => { const result = await fetchTraceRecords(baseOptions); - expect(result.success).toBe(false); - expect(result.error).toContain('Log group'); - expect(result.error).toContain('not found'); + assert(!result.success); + expect(result.error.message).toContain('Log group'); + expect(result.error.message).toContain('not found'); }); }); @@ -212,7 +216,7 @@ describe('getTrace', () => { endTime: 2000000, }); - expect(result.success).toBe(true); + assert(result.success); expect(result.filePath).toContain('test-trace.json'); const content = JSON.parse(readFileSync(outputPath, 'utf-8')); expect(content).toHaveLength(1); @@ -231,8 +235,13 @@ describe('getTrace', () => { endTime: 2000000, }); +<<<<<<< HEAD expect(result.success).toBe(false); expect(result.error).toContain('Invalid trace ID format'); +======= + assert(!result.success); + expect(result.error.message).toContain('Invalid trace ID format'); +>>>>>>> origin/main expect(existsSync(outputPath)).toBe(false); }); }); diff --git a/src/cli/operations/traces/__tests__/list-traces.test.ts b/src/cli/operations/traces/__tests__/list-traces.test.ts index 0bbe884de..ab6c8b9b9 100644 --- a/src/cli/operations/traces/__tests__/list-traces.test.ts +++ b/src/cli/operations/traces/__tests__/list-traces.test.ts @@ -1,5 +1,7 @@ +import { ResourceNotFoundError } from '../../../../lib'; import { listTraces } from '../list-traces'; import type { ListTracesOptions } from '../types'; +import assert from 'node:assert'; import { afterEach, describe, expect, it, vi } from 'vitest'; const { mockRunInsightsQuery } = vi.hoisted(() => ({ @@ -38,15 +40,15 @@ describe('listTraces', () => { const result = await listTraces(baseOptions); - expect(result.success).toBe(true); + assert(result.success); expect(result.traces).toHaveLength(2); - expect(result.traces![0]).toEqual({ + expect(result.traces[0]).toEqual({ traceId: 'trace-1', timestamp: '2024-01-01T00:05:00Z', sessionId: 'sess-1', spanCount: '12', }); - expect(result.traces![1]).toEqual({ + expect(result.traces[1]).toEqual({ traceId: 'trace-2', timestamp: '2024-01-01T00:03:00Z', sessionId: undefined, @@ -66,9 +68,9 @@ describe('listTraces', () => { const result = await listTraces(baseOptions); - expect(result.success).toBe(true); + assert(result.success); expect(result.traces).toHaveLength(1); - expect(result.traces![0]!.traceId).toBe('trace-1'); + expect(result.traces[0]!.traceId).toBe('trace-1'); }); it('falls back to firstSeen when lastSeen is missing', async () => { @@ -79,8 +81,8 @@ describe('listTraces', () => { const result = await listTraces(baseOptions); - expect(result.success).toBe(true); - expect(result.traces![0]!.timestamp).toBe('2024-01-01T00:00:00Z'); + assert(result.success); + expect(result.traces[0]!.timestamp).toBe('2024-01-01T00:00:00Z'); }); it('returns empty traces for empty query results', async () => { @@ -91,20 +93,20 @@ describe('listTraces', () => { const result = await listTraces(baseOptions); - expect(result.success).toBe(true); + assert(result.success); expect(result.traces).toHaveLength(0); }); it('propagates errors from runInsightsQuery', async () => { mockRunInsightsQuery.mockResolvedValueOnce({ success: false, - error: 'Log group not found', + error: new ResourceNotFoundError('Log group not found'), }); const result = await listTraces(baseOptions); - expect(result.success).toBe(false); - expect(result.error).toBe('Log group not found'); + assert(!result.success); + expect(result.error.message).toBe('Log group not found'); }); it('passes correct log group name and default limit', async () => { diff --git a/src/cli/operations/traces/get-trace.ts b/src/cli/operations/traces/get-trace.ts index a87f10a65..153e67f5c 100644 --- a/src/cli/operations/traces/get-trace.ts +++ b/src/cli/operations/traces/get-trace.ts @@ -1,3 +1,5 @@ +import { ResourceNotFoundError, ValidationError } from '../../../lib'; +import type { Result } from '../../../lib/result'; import { DEFAULT_ENDPOINT_NAME } from '../../constants'; import { runInsightsQuery } from './insights-query'; import type { @@ -23,9 +25,12 @@ async function fetchSpans( traceId: string, startTime?: number, endTime?: number -): Promise<{ success: boolean; spans?: CloudWatchSpanRecord[]; error?: string }> { +): Promise> { if (!TRACE_ID_PATTERN.test(traceId)) { - return { success: false, error: 'Invalid trace ID format. Expected a hex string (e.g., abc123def456).' }; + return { + success: false, + error: new ValidationError('Invalid trace ID format. Expected a hex string (e.g., abc123def456).'), + }; } const result = await runInsightsQuery({ @@ -50,7 +55,7 @@ async function fetchSpans( if (!result.success) return { success: false, error: result.error }; - const spans: CloudWatchSpanRecord[] = (result.rows ?? []) + const spans: CloudWatchSpanRecord[] = result.rows .filter(row => row.traceId && row.spanId) .map(row => ({ traceId: row.traceId!, @@ -82,7 +87,10 @@ export async function fetchTraceRecords(options: FetchTraceRecordsOptions): Prom const { region, runtimeId, traceId, includeSpans } = options; if (!TRACE_ID_PATTERN.test(traceId)) { - return { success: false, error: 'Invalid trace ID format. Expected a hex string (e.g., abc123def456).' }; + return { + success: false, + error: new ValidationError('Invalid trace ID format. Expected a hex string (e.g., abc123def456).'), + }; } const [recordsResult, spansResult] = await Promise.all([ @@ -103,10 +111,10 @@ export async function fetchTraceRecords(options: FetchTraceRecordsOptions): Prom return { success: false, error: recordsResult.error }; } - const traceData = recordsResult.rows ?? []; + const traceData = recordsResult.rows; - if (traceData.length === 0 && (!spansResult || (spansResult.spans ?? []).length === 0)) { - return { success: false, error: `No trace data found for trace ID: ${traceId}` }; + if (traceData.length === 0 && (!spansResult || !spansResult.success || spansResult.spans.length === 0)) { + return { success: false, error: new ResourceNotFoundError(`No trace data found for trace ID: ${traceId}`) }; } const records: CloudWatchTraceRecord[] = traceData.map(entry => { @@ -129,11 +137,9 @@ export async function fetchTraceRecords(options: FetchTraceRecordsOptions): Prom return record; }); - const result: FetchTraceRecordsResult = { success: true, records }; - - if (spansResult?.success && spansResult.spans) { - result.spans = spansResult.spans; - } + const result: FetchTraceRecordsResult = spansResult?.success + ? { success: true, records, spans: spansResult.spans } + : { success: true, records }; return result; } @@ -146,7 +152,10 @@ export async function getTrace(options: GetTraceOptions): Promise { diff --git a/src/cli/operations/traces/insights-query.ts b/src/cli/operations/traces/insights-query.ts index 5a4da2031..5d9c30b3a 100644 --- a/src/cli/operations/traces/insights-query.ts +++ b/src/cli/operations/traces/insights-query.ts @@ -1,3 +1,5 @@ +import { ResourceNotFoundError, TimeoutError, toError } from '../../../lib'; +import type { Result } from '../../../lib/result'; import { getCredentialProvider } from '../../aws'; import { CloudWatchLogsClient, GetQueryResultsCommand, StartQueryCommand } from '@aws-sdk/client-cloudwatch-logs'; @@ -11,11 +13,7 @@ export interface InsightsQueryOptions { endTime?: number; } -export interface InsightsQueryResult { - success: boolean; - rows?: Record[]; - error?: string; -} +export type InsightsQueryResult = Result<{ rows: Record[] }>; async function pollQueryResults(client: CloudWatchLogsClient, queryId: string): Promise { for (let i = 0; i < 60; i++) { @@ -26,7 +24,7 @@ async function pollQueryResults(client: CloudWatchLogsClient, queryId: string): if (status === 'Complete' || status === 'Failed' || status === 'Cancelled') { if (status !== 'Complete') { - return { success: false, error: `Query ${status.toLowerCase()}` }; + return { success: false, error: new Error(`Query ${status.toLowerCase()}`) }; } const rows = (queryResults.results ?? []).map(row => { @@ -42,7 +40,7 @@ async function pollQueryResults(client: CloudWatchLogsClient, queryId: string): } } - return { success: false, error: 'Query timed out after 60 seconds' }; + return { success: false, error: new TimeoutError('Query timed out after 60 seconds') }; } export async function runInsightsQuery(options: InsightsQueryOptions): Promise { @@ -68,7 +66,7 @@ export async function runInsightsQuery(options: InsightsQueryOptions): Promise((acc, row) => { + const traces = result.rows.reduce((acc, row) => { if (row.traceId) { acc.push({ traceId: row.traceId, diff --git a/src/cli/operations/traces/types.ts b/src/cli/operations/traces/types.ts index fae88a83b..95afe1a1e 100644 --- a/src/cli/operations/traces/types.ts +++ b/src/cli/operations/traces/types.ts @@ -1,3 +1,5 @@ +import type { Result } from '../../../lib/result'; + export interface CloudWatchTraceRecord { '@timestamp': string; '@message': unknown; @@ -31,12 +33,10 @@ export interface FetchTraceRecordsOptions { includeSpans?: boolean; } -export interface FetchTraceRecordsResult { - success: boolean; - records?: CloudWatchTraceRecord[]; +export type FetchTraceRecordsResult = Result<{ + records: CloudWatchTraceRecord[]; spans?: CloudWatchSpanRecord[]; - error?: string; -} +}>; export interface GetTraceOptions { region: string; @@ -48,11 +48,7 @@ export interface GetTraceOptions { endTime?: number; } -export interface GetTraceResult { - success: boolean; - filePath?: string; - error?: string; -} +export type GetTraceResult = Result<{ filePath: string }>; export interface TraceEntry { traceId: string; @@ -70,8 +66,4 @@ export interface ListTracesOptions { endTime?: number; } -export interface ListTracesResult { - success: boolean; - traces?: TraceEntry[]; - error?: string; -} +export type ListTracesResult = Result<{ traces: TraceEntry[] }>; diff --git a/src/cli/primitives/ABTestPrimitive.ts b/src/cli/primitives/ABTestPrimitive.ts index dc1852c1f..c33ec83fe 100644 --- a/src/cli/primitives/ABTestPrimitive.ts +++ b/src/cli/primitives/ABTestPrimitive.ts @@ -1,8 +1,17 @@ +<<<<<<< HEAD import { findConfigRoot } from '../../lib'; import type { ABTest } from '../../schema/schemas/primitives/ab-test'; import { ABTestSchema } from '../../schema/schemas/primitives/ab-test'; import { getErrorMessage } from '../errors'; import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/remove/types'; +======= +import { ResourceNotFoundError, findConfigRoot, serializeResult, toError } from '../../lib'; +import type { Result } from '../../lib/result'; +import type { ABTest } from '../../schema/schemas/primitives/ab-test'; +import { ABTestSchema } from '../../schema/schemas/primitives/ab-test'; +import { getErrorMessage } from '../errors'; +import type { RemovalPreview, SchemaChange } from '../operations/remove/types'; +>>>>>>> origin/main import { withCommandRunTelemetry } from '../telemetry/cli-command-run.js'; import { requireTTY } from '../tui/guards/tty'; import { BasePrimitive } from './BasePrimitive'; @@ -66,17 +75,29 @@ export class ABTestPrimitive extends BasePrimitive { +======= + return { success: false, error: toError(err) }; + } + } + + async remove(testName: string, options?: { deleteGateway?: boolean }): Promise { +>>>>>>> origin/main try { const project = await this.readProjectSpec(); const index = (project.abTests ?? []).findIndex(t => t.name === testName); if (index === -1) { +<<<<<<< HEAD return { success: false, error: `AB test "${testName}" not found.` }; +======= + return { success: false, error: new ResourceNotFoundError(`AB test "${testName}" not found.`) }; +>>>>>>> origin/main } const removedTest = project.abTests[index]!; @@ -124,7 +145,11 @@ export class ABTestPrimitive extends BasePrimitive>>>>>> origin/main } } @@ -365,11 +390,19 @@ Target-Based Mode (--mode target-based) }); if (cliOptions.json) { +<<<<<<< HEAD console.log(JSON.stringify(result)); } else if (result.success) { console.log(`Added target-based AB test '${result.abTestName}'`); } else { console.error(result.error); +======= + console.log(JSON.stringify(serializeResult(result))); + } else if (result.success) { + console.log(`Added target-based AB test '${result.abTestName}'`); + } else { + console.error(result.error.message); +>>>>>>> origin/main } process.exit(result.success ? 0 : 1); return; @@ -412,11 +445,19 @@ Target-Based Mode (--mode target-based) }); if (cliOptions.json) { +<<<<<<< HEAD console.log(JSON.stringify(result)); } else if (result.success) { console.log(`Added AB test '${result.abTestName}'`); } else { console.error(result.error); +======= + console.log(JSON.stringify(serializeResult(result))); + } else if (result.success) { + console.log(`Added AB test '${result.abTestName}'`); + } else { + console.error(result.error.message); +>>>>>>> origin/main } process.exit(result.success ? 0 : 1); } else { @@ -478,7 +519,11 @@ Target-Based Mode (--mode target-based) resourceType: this.kind, resourceName: cliOptions.name, message: result.success ? `Removed ${this.label.toLowerCase()} '${cliOptions.name}'` : undefined, +<<<<<<< HEAD error: !result.success ? result.error : undefined, +======= + error: !result.success ? result.error.message : undefined, +>>>>>>> origin/main }) ); process.exit(result.success ? 0 : 1); @@ -606,7 +651,11 @@ Target-Based Mode (--mode target-based) const abTest = await this.createTargetBasedABTest(options); return { success: true, abTestName: abTest.name }; } catch (err) { +<<<<<<< HEAD return { success: false, error: getErrorMessage(err) }; +======= + return { success: false, error: toError(err) }; +>>>>>>> origin/main } } diff --git a/src/cli/primitives/AgentPrimitive.tsx b/src/cli/primitives/AgentPrimitive.tsx index 283e91ce2..ebf29ac2c 100644 --- a/src/cli/primitives/AgentPrimitive.tsx +++ b/src/cli/primitives/AgentPrimitive.tsx @@ -1,4 +1,15 @@ -import { APP_DIR, ConfigIO, NoProjectError, findConfigRoot, setEnvVar } from '../../lib'; +import { + APP_DIR, + ConfigIO, + ConflictError, + NoProjectError, + ResourceNotFoundError, + findConfigRoot, + serializeResult, + setEnvVar, + toError, +} from '../../lib'; +import type { Result } from '../../lib/result'; import type { AgentEnvSpec, BuildType, @@ -34,7 +45,11 @@ import { } from '../operations/agent/generate'; import { executeImportAgent } from '../operations/agent/import'; import { setupPythonProject } from '../operations/python'; +<<<<<<< HEAD import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/remove/types'; +======= +import type { RemovalPreview, SchemaChange } from '../operations/remove/types'; +>>>>>>> origin/main import { runCliCommand } from '../telemetry/cli-command-run.js'; import { AgentType, @@ -119,13 +134,13 @@ export class AgentPrimitive extends BasePrimitive { + async remove(agentName: string): Promise { try { const project = await this.readProjectSpec(); const agentIndex = project.runtimes.findIndex(a => a.name === agentName); if (agentIndex === -1) { - return { success: false, error: `Agent "${agentName}" not found.` }; + return { success: false, error: new ResourceNotFoundError(`Agent "${agentName}" not found.`) }; } // Remove agent (credentials preserved for potential reuse) @@ -164,8 +181,7 @@ export class AgentPrimitive extends BasePrimitive>>>>>> origin/main } else { console.log(`Added agent '${result.agentName}'`); if (result.agentPath) { diff --git a/src/cli/primitives/BasePrimitive.ts b/src/cli/primitives/BasePrimitive.ts index 149611c4f..9bc6e4a50 100644 --- a/src/cli/primitives/BasePrimitive.ts +++ b/src/cli/primitives/BasePrimitive.ts @@ -1,10 +1,13 @@ import { ConfigIO, findConfigRoot } from '../../lib'; +import type { Result } from '../../lib/result'; import type { AgentCoreProjectSpec } from '../../schema'; import type { ResourceType } from '../commands/remove/types'; import { getErrorMessage } from '../errors'; +import { withCommandRunTelemetry } from '../telemetry/cli-command-run.js'; +import type { SubCommand } from '../telemetry/schemas/command-run.js'; import { requireTTY } from '../tui/guards/tty'; import { SOURCE_CODE_NOTE } from './constants'; -import type { AddResult, AddScreenComponent, RemovableResource, RemovalPreview, RemovalResult } from './types'; +import type { AddResult, AddScreenComponent, RemovableResource, RemovalPreview } from './types'; import type { Command } from '@commander-js/extra-typings'; import type { z } from 'zod'; @@ -42,7 +45,7 @@ export abstract class BasePrimitive< /** * Remove a resource by name. */ - abstract remove(name: string): Promise; + abstract remove(name: string): Promise; /** * Preview what will be removed. @@ -120,7 +123,11 @@ export abstract class BasePrimitive< process.exit(1); } - const result = await this.remove(cliOptions.name); + const result = await withCommandRunTelemetry, Result>( + `remove.${this.kind}`, + {}, + () => this.remove(cliOptions.name!) + ); console.log( JSON.stringify({ success: result.success, @@ -128,7 +135,7 @@ export abstract class BasePrimitive< resourceName: cliOptions.name, message: result.success ? `Removed ${this.label.toLowerCase()} '${cliOptions.name}'` : undefined, note: result.success ? SOURCE_CODE_NOTE : undefined, - error: !result.success ? result.error : undefined, + error: !result.success ? result.error.message : undefined, }) ); process.exit(result.success ? 0 : 1); diff --git a/src/cli/primitives/ConfigBundlePrimitive.ts b/src/cli/primitives/ConfigBundlePrimitive.ts index 77f26205b..5f663b0a4 100644 --- a/src/cli/primitives/ConfigBundlePrimitive.ts +++ b/src/cli/primitives/ConfigBundlePrimitive.ts @@ -1,8 +1,17 @@ +<<<<<<< HEAD import { findConfigRoot } from '../../lib'; import type { ConfigBundle } from '../../schema'; import { ConfigBundleSchema } from '../../schema'; import { getErrorMessage } from '../errors'; import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/remove/types'; +======= +import { ResourceNotFoundError, findConfigRoot, serializeResult, toError } from '../../lib'; +import type { Result } from '../../lib/result'; +import type { ConfigBundle } from '../../schema'; +import { ConfigBundleSchema } from '../../schema'; +import { getErrorMessage } from '../errors'; +import type { RemovalPreview, SchemaChange } from '../operations/remove/types'; +>>>>>>> origin/main import { BasePrimitive } from './BasePrimitive'; import type { AddResult, AddScreenComponent, RemovableResource } from './types'; import type { Command } from '@commander-js/extra-typings'; @@ -37,17 +46,29 @@ export class ConfigBundlePrimitive extends BasePrimitive { +======= + return { success: false, error: toError(err) }; + } + } + + async remove(bundleName: string): Promise { +>>>>>>> origin/main try { const project = await this.readProjectSpec(); const index = (project.configBundles ?? []).findIndex(b => b.name === bundleName); if (index === -1) { +<<<<<<< HEAD return { success: false, error: `Configuration bundle "${bundleName}" not found.` }; +======= + return { success: false, error: new ResourceNotFoundError(`Configuration bundle "${bundleName}" not found.`) }; +>>>>>>> origin/main } project.configBundles.splice(index, 1); @@ -55,7 +76,11 @@ export class ConfigBundlePrimitive extends BasePrimitive>>>>>> origin/main } } @@ -170,11 +195,19 @@ export class ConfigBundlePrimitive extends BasePrimitive>>>>>> origin/main } process.exit(result.success ? 0 : 1); } else { diff --git a/src/cli/primitives/CredentialPrimitive.tsx b/src/cli/primitives/CredentialPrimitive.tsx index 17d77c458..6e8abab1b 100644 --- a/src/cli/primitives/CredentialPrimitive.tsx +++ b/src/cli/primitives/CredentialPrimitive.tsx @@ -1,9 +1,22 @@ -import { findConfigRoot, getEnvVar, setEnvVar } from '../../lib'; +import { + ConflictError, + ResourceNotFoundError, + findConfigRoot, + getEnvVar, + serializeResult, + setEnvVar, + toError, +} from '../../lib'; +import type { Result } from '../../lib/result'; import type { Credential, ModelProvider } from '../../schema'; import { CredentialSchema } from '../../schema'; import { validateAddCredentialOptions } from '../commands/add/validate'; import { getErrorMessage } from '../errors'; +<<<<<<< HEAD import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/remove/types'; +======= +import type { RemovalPreview, SchemaChange } from '../operations/remove/types'; +>>>>>>> origin/main import { runCliCommand } from '../telemetry/cli-command-run.js'; import { CredentialType, standardize } from '../telemetry/schemas/common-shapes.js'; import { requireTTY } from '../tui/guards/tty'; @@ -77,17 +90,17 @@ export class CredentialPrimitive extends BasePrimitive { + async remove(credentialName: string, options?: { force?: boolean }): Promise { try { const project = await this.readProjectSpec(); const credentialIndex = project.credentials.findIndex(c => c.name === credentialName); if (credentialIndex === -1) { - return { success: false, error: `Credential "${credentialName}" not found.` }; + return { success: false, error: new ResourceNotFoundError(`Credential "${credentialName}" not found.`) }; } const credential = project.credentials[credentialIndex]!; @@ -96,7 +109,9 @@ export class CredentialPrimitive extends BasePrimitive t.name).join(', '); return { success: false, - error: `Credential "${credentialName}" is referenced by gateway target(s): ${targetList}. Use force to override.`, + error: new ConflictError( + `Credential "${credentialName}" is referenced by gateway target(s): ${targetList}. Use force to override.` + ), }; } @@ -115,8 +132,7 @@ export class CredentialPrimitive extends BasePrimitive>>>>>> origin/main return { credential_type: standardize(CredentialType, cliOptions.type ?? 'api-key'), }; diff --git a/src/cli/primitives/EvaluatorPrimitive.ts b/src/cli/primitives/EvaluatorPrimitive.ts index ed255c061..f2142626b 100644 --- a/src/cli/primitives/EvaluatorPrimitive.ts +++ b/src/cli/primitives/EvaluatorPrimitive.ts @@ -1,8 +1,13 @@ -import { findConfigRoot } from '../../lib'; +import { ConflictError, ResourceNotFoundError, findConfigRoot, serializeResult, toError } from '../../lib'; +import type { Result } from '../../lib/result'; import type { EvaluationLevel, Evaluator, EvaluatorConfig } from '../../schema'; -import { EvaluationLevelSchema, EvaluatorSchema } from '../../schema'; +import { EvaluationLevelSchema, EvaluatorSchema, isValidKmsKeyArn } from '../../schema'; import { getErrorMessage } from '../errors'; +<<<<<<< HEAD import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/remove/types'; +======= +import type { RemovalPreview, SchemaChange } from '../operations/remove/types'; +>>>>>>> origin/main import { runCliCommand } from '../telemetry/cli-command-run.js'; import { EvaluatorType, Level, standardize } from '../telemetry/schemas/common-shapes.js'; import { renderCodeBasedEvaluatorTemplate } from '../templates/EvaluatorRenderer'; @@ -25,6 +30,7 @@ export interface AddEvaluatorOptions { level: EvaluationLevel; description?: string; config: EvaluatorConfig; + kmsKeyArn?: string; } export type RemovableEvaluator = RemovableResource; @@ -57,17 +63,17 @@ export class EvaluatorPrimitive extends BasePrimitive { + async remove(evaluatorName: string): Promise { try { const project = await this.readProjectSpec(); const index = project.evaluators.findIndex(e => e.name === evaluatorName); if (index === -1) { - return { success: false, error: `Evaluator "${evaluatorName}" not found.` }; + return { success: false, error: new ResourceNotFoundError(`Evaluator "${evaluatorName}" not found.`) }; } // Warn if referenced by online eval configs @@ -76,7 +82,9 @@ export class EvaluatorPrimitive extends BasePrimitive c.name).join(', '); return { success: false, - error: `Evaluator "${evaluatorName}" is referenced by online eval config(s): ${configNames}. Remove those references first.`, + error: new ConflictError( + `Evaluator "${evaluatorName}" is referenced by online eval config(s): ${configNames}. Remove those references first.` + ), }; } @@ -96,7 +104,7 @@ export class EvaluatorPrimitive extends BasePrimitive', 'Path to evaluator config JSON file (overrides --model, --instructions, --rating-scale) [non-interactive]' ) + .option('--kms-key-arn ', 'KMS key ARN for evaluator encryption (optional)') .option('--json', 'Output as JSON [non-interactive]') .action( async (cliOptions: { @@ -196,6 +205,7 @@ export class EvaluatorPrimitive extends BasePrimitive { if (!findConfigRoot()) { @@ -289,18 +299,33 @@ export class EvaluatorPrimitive extends BasePrimitive>>>>>> origin/main } else { if (result.codePath) { console.log(`Created evaluator '${result.evaluatorName}'`); @@ -386,6 +411,7 @@ export class EvaluatorPrimitive extends BasePrimitive>>>>>> origin/main import { runCliCommand, withCommandRunTelemetry } from '../telemetry/cli-command-run.js'; import { AuthorizerType, PolicyEngineMode, standardize } from '../telemetry/schemas/common-shapes.js'; import { requireTTY } from '../tui/guards/tty'; @@ -67,18 +72,18 @@ export class GatewayPrimitive extends BasePrimitive { + async remove(gatewayName: string): Promise { try { const project = await this.readProjectSpec(); const mcpSpec = extractMcpSpec(project); const gateway = mcpSpec.agentCoreGateways.find(g => g.name === gatewayName); if (!gateway) { - return { success: false, error: `Gateway "${gatewayName}" not found.` }; + return { success: false, error: new ResourceNotFoundError(`Gateway "${gatewayName}" not found.`) }; } const newMcpSpec = this.computeRemovedGatewayMcpSpec(mcpSpec, gatewayName); @@ -86,8 +91,7 @@ export class GatewayPrimitive extends BasePrimitive>>>>>> origin/main } else { console.log(`Added gateway '${result.gatewayName}'`); } @@ -270,7 +282,7 @@ export class GatewayPrimitive extends BasePrimitive>>>>>> origin/main import { runCliCommand, withCommandRunTelemetry } from '../telemetry/cli-command-run.js'; import { GATEWAY_TARGET_TYPE_MAP, @@ -76,16 +89,16 @@ export class GatewayTargetPrimitive extends BasePrimitive { + async remove(name: string): Promise { // Find the target by name to get its gateway info const tools = await this.getRemovable(); const tool = tools.find(t => t.name === name); if (!tool) { - return { success: false, error: `Gateway target "${name}" not found.` }; + return { success: false, error: new ResourceNotFoundError(`Gateway target "${name}" not found.`) }; } return this.removeGatewayTarget(tool); } @@ -183,7 +196,7 @@ export class GatewayTargetPrimitive extends BasePrimitive { + async removeGatewayTarget(tool: RemovableGatewayTarget): Promise { try { const project = await this.readProjectSpec(); const mcpSpec = extractMcpSpec(project); @@ -195,11 +208,14 @@ export class GatewayTargetPrimitive extends BasePrimitive g.name === tool.gatewayName); if (!gateway) { - return { success: false, error: `Gateway "${tool.gatewayName}" not found.` }; + return { success: false, error: new ResourceNotFoundError(`Gateway "${tool.gatewayName}" not found.`) }; } const target = gateway.targets.find(t => t.name === tool.name); if (!target) { - return { success: false, error: `Target "${tool.name}" not found in gateway "${tool.gatewayName}".` }; + return { + success: false, + error: new ResourceNotFoundError(`Target "${tool.name}" not found in gateway "${tool.gatewayName}".`), + }; } if (target.compute?.implementation && 'path' in target.compute.implementation) { toolPath = target.compute.implementation.path; @@ -223,8 +239,7 @@ export class GatewayTargetPrimitive extends BasePrimitive>>>>>> origin/main } else { console.log(`Added gateway target '${result.toolName}'`); if (result.sourcePath) { @@ -520,7 +543,7 @@ export class GatewayTargetPrimitive extends BasePrimitive>>>>>> origin/main import { runCliCommand } from '../telemetry/cli-command-run.js'; import { requireTTY } from '../tui/guards/tty'; import { DEFAULT_EVENT_EXPIRY } from '../tui/screens/memory/types'; @@ -83,17 +88,17 @@ export class MemoryPrimitive extends BasePrimitive { + async remove(memoryName: string): Promise { try { const project = await this.readProjectSpec(); const memoryIndex = project.memories.findIndex(m => m.name === memoryName); if (memoryIndex === -1) { - return { success: false, error: `Memory "${memoryName}" not found.` }; + return { success: false, error: new ResourceNotFoundError(`Memory "${memoryName}" not found.`) }; } project.memories.splice(memoryIndex, 1); @@ -101,8 +106,7 @@ export class MemoryPrimitive extends BasePrimitive>>>>>> origin/main const strategyList = (cliOptions.strategies ?? '') .split(',') .map(s => s.trim().toUpperCase()) diff --git a/src/cli/primitives/OnlineEvalConfigPrimitive.ts b/src/cli/primitives/OnlineEvalConfigPrimitive.ts index 0b77cbba5..fd93048ff 100644 --- a/src/cli/primitives/OnlineEvalConfigPrimitive.ts +++ b/src/cli/primitives/OnlineEvalConfigPrimitive.ts @@ -1,8 +1,13 @@ -import { findConfigRoot } from '../../lib'; +import { ResourceNotFoundError, findConfigRoot, serializeResult, toError } from '../../lib'; +import type { Result } from '../../lib/result'; import type { OnlineEvalConfig } from '../../schema'; import { OnlineEvalConfigSchema } from '../../schema'; import { getErrorMessage } from '../errors'; +<<<<<<< HEAD import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/remove/types'; +======= +import type { RemovalPreview, SchemaChange } from '../operations/remove/types'; +>>>>>>> origin/main import { runCliCommand } from '../telemetry/cli-command-run.js'; import { requireTTY } from '../tui/guards/tty'; import { BasePrimitive } from './BasePrimitive'; @@ -34,17 +39,17 @@ export class OnlineEvalConfigPrimitive extends BasePrimitive { + async remove(configName: string): Promise { try { const project = await this.readProjectSpec(); const index = project.onlineEvalConfigs.findIndex(c => c.name === configName); if (index === -1) { - return { success: false, error: `Online eval config "${configName}" not found.` }; + return { success: false, error: new ResourceNotFoundError(`Online eval config "${configName}" not found.`) }; } project.onlineEvalConfigs.splice(index, 1); @@ -52,7 +57,7 @@ export class OnlineEvalConfigPrimitive extends BasePrimitive>>>>>> origin/main return { evaluator_count: allEvaluators.length, enable_on_create: cliOptions.enableOnCreate ?? false, diff --git a/src/cli/primitives/PolicyEnginePrimitive.ts b/src/cli/primitives/PolicyEnginePrimitive.ts index bab91022a..41b3ce954 100644 --- a/src/cli/primitives/PolicyEnginePrimitive.ts +++ b/src/cli/primitives/PolicyEnginePrimitive.ts @@ -1,8 +1,13 @@ -import { findConfigRoot } from '../../lib'; +import { ResourceNotFoundError, findConfigRoot, serializeResult, toError } from '../../lib'; +import type { Result } from '../../lib/result'; import type { AgentCoreProjectSpec, PolicyEngine } from '../../schema'; import { PolicyEngineModeSchema, PolicyEngineSchema } from '../../schema'; import { getErrorMessage } from '../errors'; +<<<<<<< HEAD import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/remove/types'; +======= +import type { RemovalPreview, SchemaChange } from '../operations/remove/types'; +>>>>>>> origin/main import { runCliCommand, withCommandRunTelemetry } from '../telemetry/cli-command-run.js'; import { AttachMode, standardize } from '../telemetry/schemas/common-shapes.js'; import { requireTTY } from '../tui/guards/tty'; @@ -40,17 +45,17 @@ export class PolicyEnginePrimitive extends BasePrimitive { + async remove(engineName: string): Promise { try { const project = await this.readProjectSpec(); const index = project.policyEngines.findIndex(e => e.name === engineName); if (index === -1) { - return { success: false, error: `Policy engine "${engineName}" not found.` }; + return { success: false, error: new ResourceNotFoundError(`Policy engine "${engineName}" not found.`) }; } project.policyEngines.splice(index, 1); @@ -70,8 +75,7 @@ export class PolicyEnginePrimitive extends BasePrimitive>>>>>> origin/main const gatewayCount = cliOptions.attachToGateways ? cliOptions.attachToGateways .split(',') @@ -327,13 +343,13 @@ export class PolicyEnginePrimitive extends BasePrimitive>>>>>> origin/main import { runCliCommand, withCommandRunTelemetry } from '../telemetry/cli-command-run.js'; import { ValidationMode, standardize } from '../telemetry/schemas/common-shapes.js'; import { requireTTY } from '../tui/guards/tty'; @@ -40,7 +45,7 @@ export class PolicyPrimitive extends BasePrimitive 1) { return { success: false, - error: 'Only one of --statement, --source, or --generate can be provided.', + error: new ValidationError('Only one of --statement, --source, or --generate can be provided.'), }; } @@ -48,7 +53,7 @@ export class PolicyPrimitive extends BasePrimitive e.name === options.engine); if (!engine) { - return { success: false, error: `Policy engine "${options.engine}" not found.` }; + return { success: false, error: new ResourceNotFoundError(`Policy engine "${options.engine}" not found.`) }; } this.checkDuplicate(engine.policies, options.name, 'Policy'); @@ -57,11 +62,11 @@ export class PolicyPrimitive extends BasePrimitive to specify one.', + error: new ResourceNotFoundError( + 'No deployed gateway found. Policy generation requires a deployed gateway. Use --gateway to specify one.' + ), }; } @@ -123,7 +134,10 @@ export class PolicyPrimitive extends BasePrimitive { + async remove(nameOrCompositeKey: string, engineName?: string): Promise { try { const project = await this.readProjectSpec(); @@ -167,7 +181,9 @@ export class PolicyPrimitive extends BasePrimitive 1) { return { success: false, - error: `Policy "${resolvedPolicy}" exists in multiple engines: ${matchingEngines.map(e => e.name).join(', ')}. Use --engine to specify which one.`, + error: new ValidationError( + `Policy "${resolvedPolicy}" exists in multiple engines: ${matchingEngines.map(e => e.name).join(', ')}. Use --engine to specify which one.` + ), }; } } @@ -185,10 +201,12 @@ export class PolicyPrimitive extends BasePrimitive>>>>>> origin/main const sourceType: 'file' | 'statement' | 'generate' = cliOptions.source ? 'file' : cliOptions.generate @@ -410,13 +440,13 @@ export class PolicyPrimitive extends BasePrimitive>>>>>> origin/main import { BasePrimitive } from './BasePrimitive'; import { SOURCE_CODE_NOTE } from './constants'; import type { AddResult, AddScreenComponent, RemovableResource } from './types'; @@ -46,7 +59,7 @@ export class RuntimeEndpointPrimitive extends BasePrimitive a.name === options.runtime); if (!runtime) { - return { success: false, error: `Runtime "${options.runtime}" not found.` }; + return { success: false, error: new ResourceNotFoundError(`Runtime "${options.runtime}" not found.`) }; } // Initialize endpoints dictionary if needed @@ -56,14 +69,14 @@ export class RuntimeEndpointPrimitive extends BasePrimitive deployedRuntime.runtimeVersion) { return { success: false, - error: `Version ${version} exceeds latest deployed version ${deployedRuntime.runtimeVersion} for runtime "${options.runtime}".`, + error: new ValidationError( + `Version ${version} exceeds latest deployed version ${deployedRuntime.runtimeVersion} for runtime "${options.runtime}".` + ), }; } } @@ -104,11 +119,11 @@ export class RuntimeEndpointPrimitive extends BasePrimitive { + async remove(name: string): Promise { try { const project = await this.readProjectSpec(); @@ -119,7 +134,7 @@ export class RuntimeEndpointPrimitive extends BasePrimitive r.name === runtimeName); if (!runtime?.endpoints?.[endpointName]) { - return { success: false, error: `Runtime endpoint "${name}" not found.` }; + return { success: false, error: new ResourceNotFoundError(`Runtime endpoint "${name}" not found.`) }; } delete runtime.endpoints[endpointName]; if (Object.keys(runtime.endpoints).length === 0) { @@ -141,9 +156,9 @@ export class RuntimeEndpointPrimitive extends BasePrimitive>>>>>> origin/main } else { console.log(`Added runtime endpoint '${cliOptions.endpoint}' to runtime '${cliOptions.runtime}'`); } @@ -296,7 +319,9 @@ export class RuntimeEndpointPrimitive extends BasePrimitive + this.remove(cliOptions.name!) + ); console.log( JSON.stringify({ success: result.success, @@ -304,7 +329,7 @@ export class RuntimeEndpointPrimitive extends BasePrimitive ({ writeProjectSpec = mockWriteProjectSpec; }, findConfigRoot: () => '/fake/root', +<<<<<<< HEAD +======= + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), + serializeResult: (r: unknown) => r, + ResourceNotFoundError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ResourceNotFoundError'; + } + }, +>>>>>>> origin/main })); function makeProject(abTests: { name: string; gatewayRef?: string }[] = []) { @@ -121,7 +132,14 @@ describe('ABTestPrimitive', () => { const result = await primitive.add(validOptions); expect(result).toEqual( +<<<<<<< HEAD expect.objectContaining({ success: false, error: expect.stringContaining('already exists') }) +======= + expect.objectContaining({ + success: false, + error: expect.objectContaining({ message: expect.stringContaining('already exists') }), + }) +>>>>>>> origin/main ); }); @@ -130,7 +148,11 @@ describe('ABTestPrimitive', () => { const result = await primitive.add(validOptions); +<<<<<<< HEAD expect(result).toEqual(expect.objectContaining({ success: false, error: 'disk read error' })); +======= + expect(result).toEqual(expect.objectContaining({ success: false, error: new Error('disk read error') })); +>>>>>>> origin/main }); it('returns error when writeProjectSpec fails', async () => { @@ -139,7 +161,11 @@ describe('ABTestPrimitive', () => { const result = await primitive.add(validOptions); +<<<<<<< HEAD expect(result).toEqual(expect.objectContaining({ success: false, error: 'disk write error' })); +======= + expect(result).toEqual(expect.objectContaining({ success: false, error: new Error('disk write error') })); +>>>>>>> origin/main }); it('returns error when variant weights do not sum to 100', async () => { @@ -175,8 +201,13 @@ describe('ABTestPrimitive', () => { expect(result.success).toBe(false); if (!result.success) { +<<<<<<< HEAD expect(result.error).toContain('NonExistent'); expect(result.error).toContain('not found'); +======= + expect(result.error.message).toContain('NonExistent'); + expect(result.error.message).toContain('not found'); +>>>>>>> origin/main } }); @@ -187,7 +218,11 @@ describe('ABTestPrimitive', () => { expect(result.success).toBe(false); if (!result.success) { +<<<<<<< HEAD expect(result.error).toBe('io error'); +======= + expect(result.error.message).toBe('io error'); +>>>>>>> origin/main } }); diff --git a/src/cli/primitives/__tests__/BasePrimitive.test.ts b/src/cli/primitives/__tests__/BasePrimitive.test.ts index 830cd4244..f29252bf0 100644 --- a/src/cli/primitives/__tests__/BasePrimitive.test.ts +++ b/src/cli/primitives/__tests__/BasePrimitive.test.ts @@ -1,5 +1,6 @@ +import type { Result } from '../../../lib/result'; import { BasePrimitive } from '../BasePrimitive'; -import type { AddResult, AddScreenComponent, RemovableResource, RemovalPreview, RemovalResult } from '../types'; +import type { AddResult, AddScreenComponent, RemovableResource, RemovalPreview } from '../types'; import type { Command } from '@commander-js/extra-typings'; import { describe, expect, it } from 'vitest'; import { z } from 'zod'; @@ -14,7 +15,7 @@ class StubPrimitive extends BasePrimitive { return Promise.resolve({ success: true }); } - remove(_name: string): Promise { + remove(_name: string): Promise { return Promise.resolve({ success: true }); } diff --git a/src/cli/primitives/__tests__/EvaluatorPrimitive.test.ts b/src/cli/primitives/__tests__/EvaluatorPrimitive.test.ts index b41545d20..a521e6a78 100644 --- a/src/cli/primitives/__tests__/EvaluatorPrimitive.test.ts +++ b/src/cli/primitives/__tests__/EvaluatorPrimitive.test.ts @@ -11,6 +11,20 @@ vi.mock('../../../lib/index.js', () => ({ writeProjectSpec = mockWriteProjectSpec; }, findConfigRoot: () => '/fake/root', + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), + serializeResult: (r: unknown) => r, + ResourceNotFoundError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ResourceNotFoundError'; + } + }, + ConflictError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ConflictError'; + } + }, })); const validConfig: EvaluatorConfig = { @@ -108,7 +122,10 @@ describe('EvaluatorPrimitive', () => { }); expect(result).toEqual( - expect.objectContaining({ success: false, error: expect.stringContaining('already exists') }) + expect.objectContaining({ + success: false, + error: expect.objectContaining({ message: expect.stringContaining('already exists') }), + }) ); }); @@ -121,7 +138,7 @@ describe('EvaluatorPrimitive', () => { config: validConfig, }); - expect(result).toEqual(expect.objectContaining({ success: false, error: 'disk read error' })); + expect(result).toEqual(expect.objectContaining({ success: false, error: new Error('disk read error') })); }); }); @@ -145,8 +162,8 @@ describe('EvaluatorPrimitive', () => { expect(result.success).toBe(false); if (!result.success) { - expect(result.error).toContain('NonExistent'); - expect(result.error).toContain('not found'); + expect(result.error.message).toContain('NonExistent'); + expect(result.error.message).toContain('not found'); } }); @@ -159,8 +176,8 @@ describe('EvaluatorPrimitive', () => { expect(result.success).toBe(false); if (!result.success) { - expect(result.error).toContain('referenced by online eval config'); - expect(result.error).toContain('MyOnlineConfig'); + expect(result.error.message).toContain('referenced by online eval config'); + expect(result.error.message).toContain('MyOnlineConfig'); } expect(mockWriteProjectSpec).not.toHaveBeenCalled(); }); @@ -172,7 +189,7 @@ describe('EvaluatorPrimitive', () => { expect(result.success).toBe(false); if (!result.success) { - expect(result.error).toBe('io error'); + expect(result.error.message).toBe('io error'); } }); }); diff --git a/src/cli/primitives/__tests__/GatewayPrimitive.test.ts b/src/cli/primitives/__tests__/GatewayPrimitive.test.ts index f812ca380..e8afe2b76 100644 --- a/src/cli/primitives/__tests__/GatewayPrimitive.test.ts +++ b/src/cli/primitives/__tests__/GatewayPrimitive.test.ts @@ -13,7 +13,10 @@ const defaultProject: AgentCoreProjectSpec = { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], @@ -35,6 +38,14 @@ vi.mock('../../../lib', () => { ConfigIO: MockConfigIO, findConfigRoot: vi.fn().mockReturnValue('/fake/root'), setEnvVar: vi.fn().mockResolvedValue(undefined), + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), + serializeResult: (r: unknown) => r, + ResourceNotFoundError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ResourceNotFoundError'; + } + }, }; }); diff --git a/src/cli/primitives/__tests__/OnlineEvalConfigPrimitive.test.ts b/src/cli/primitives/__tests__/OnlineEvalConfigPrimitive.test.ts index c81160a6c..81f13b40a 100644 --- a/src/cli/primitives/__tests__/OnlineEvalConfigPrimitive.test.ts +++ b/src/cli/primitives/__tests__/OnlineEvalConfigPrimitive.test.ts @@ -10,6 +10,14 @@ vi.mock('../../../lib/index.js', () => ({ writeProjectSpec = mockWriteProjectSpec; }, findConfigRoot: () => '/fake/root', + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), + serializeResult: (r: unknown) => r, + ResourceNotFoundError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ResourceNotFoundError'; + } + }, })); function makeProject( @@ -126,7 +134,10 @@ describe('OnlineEvalConfigPrimitive', () => { }); expect(result).toEqual( - expect.objectContaining({ success: false, error: expect.stringContaining('already exists') }) + expect.objectContaining({ + success: false, + error: expect.objectContaining({ message: expect.stringContaining('already exists') }), + }) ); }); @@ -140,7 +151,7 @@ describe('OnlineEvalConfigPrimitive', () => { samplingRate: 10, }); - expect(result).toEqual(expect.objectContaining({ success: false, error: 'no project' })); + expect(result).toEqual(expect.objectContaining({ success: false, error: new Error('no project') })); }); }); @@ -169,8 +180,8 @@ describe('OnlineEvalConfigPrimitive', () => { expect(result.success).toBe(false); if (!result.success) { - expect(result.error).toContain('NonExistent'); - expect(result.error).toContain('not found'); + expect(result.error.message).toContain('NonExistent'); + expect(result.error.message).toContain('not found'); } }); @@ -181,7 +192,7 @@ describe('OnlineEvalConfigPrimitive', () => { expect(result.success).toBe(false); if (!result.success) { - expect(result.error).toBe('io error'); + expect(result.error.message).toBe('io error'); } }); }); diff --git a/src/cli/primitives/__tests__/RuntimeEndpointPrimitive.test.ts b/src/cli/primitives/__tests__/RuntimeEndpointPrimitive.test.ts index 46fe426f5..1dc4d9e73 100644 --- a/src/cli/primitives/__tests__/RuntimeEndpointPrimitive.test.ts +++ b/src/cli/primitives/__tests__/RuntimeEndpointPrimitive.test.ts @@ -14,6 +14,26 @@ vi.mock('../../../lib/index.js', () => ({ readDeployedState = mockReadDeployedState; }, findConfigRoot: () => '/fake/root', + toError: (err: unknown) => (err instanceof Error ? err : new Error(String(err))), + serializeResult: (r: unknown) => r, + ResourceNotFoundError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ResourceNotFoundError'; + } + }, + ConflictError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ConflictError'; + } + }, + ValidationError: class extends Error { + constructor(m: string) { + super(m); + this.name = 'ValidationError'; + } + }, })); function makeProject( @@ -87,7 +107,12 @@ describe('RuntimeEndpointPrimitive', () => { endpoint: 'prod', }); - expect(result).toEqual(expect.objectContaining({ success: false, error: expect.stringContaining('not found') })); + expect(result).toEqual( + expect.objectContaining({ + success: false, + error: expect.objectContaining({ message: expect.stringContaining('not found') }), + }) + ); }); it('returns error when endpoint already exists', async () => { @@ -100,7 +125,10 @@ describe('RuntimeEndpointPrimitive', () => { }); expect(result).toEqual( - expect.objectContaining({ success: false, error: expect.stringContaining('already exists') }) + expect.objectContaining({ + success: false, + error: expect.objectContaining({ message: expect.stringContaining('already exists') }), + }) ); }); @@ -134,7 +162,10 @@ describe('RuntimeEndpointPrimitive', () => { }); expect(result).toEqual( - expect.objectContaining({ success: false, error: expect.stringContaining('positive integer') }) + expect.objectContaining({ + success: false, + error: expect.objectContaining({ message: expect.stringContaining('positive integer') }), + }) ); }); @@ -181,7 +212,10 @@ describe('RuntimeEndpointPrimitive', () => { }); expect(result).toEqual( - expect.objectContaining({ success: false, error: expect.stringContaining('exceeds latest deployed version') }) + expect.objectContaining({ + success: false, + error: expect.objectContaining({ message: expect.stringContaining('exceeds latest deployed version') }), + }) ); }); }); @@ -223,7 +257,12 @@ describe('RuntimeEndpointPrimitive', () => { const result = await primitive.remove('MyRuntime/nonexistent'); - expect(result).toEqual(expect.objectContaining({ success: false, error: expect.stringContaining('not found') })); + expect(result).toEqual( + expect.objectContaining({ + success: false, + error: expect.objectContaining({ message: expect.stringContaining('not found') }), + }) + ); }); it('cleans up empty endpoints dict after removing last endpoint', async () => { diff --git a/src/cli/primitives/__tests__/auth-utils.test.ts b/src/cli/primitives/__tests__/auth-utils.test.ts index d309df4d1..e7f6d5967 100644 --- a/src/cli/primitives/__tests__/auth-utils.test.ts +++ b/src/cli/primitives/__tests__/auth-utils.test.ts @@ -93,7 +93,10 @@ describe('createManagedOAuthCredential', () => { onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], diff --git a/src/cli/primitives/index.ts b/src/cli/primitives/index.ts index 83bff4bbb..457fbd4ec 100644 --- a/src/cli/primitives/index.ts +++ b/src/cli/primitives/index.ts @@ -26,5 +26,9 @@ export { getPrimitive, } from './registry'; export { SOURCE_CODE_NOTE } from './constants'; +<<<<<<< HEAD export type { AddResult, AddScreenComponent, RemovableResource, RemovalPreview, RemovalResult } from './types'; export type { AddHarnessOptions } from './HarnessPrimitive'; +======= +export type { AddResult, AddScreenComponent, RemovableResource, RemovalPreview, Result } from './types'; +>>>>>>> origin/main diff --git a/src/cli/primitives/types.ts b/src/cli/primitives/types.ts index 73842c972..90885ed7f 100644 --- a/src/cli/primitives/types.ts +++ b/src/cli/primitives/types.ts @@ -1,14 +1,11 @@ -import type { RemovalPreview, RemovalResult } from '../operations/remove/types'; +import type { Result } from '../../lib/result'; +import type { RemovalPreview } from '../operations/remove/types'; import type { ComponentType } from 'react'; -/** - * Result of an add operation. - * Use the generic parameter to type extra fields on the success branch: - * AddResult<{ agentName: string }> → success branch has typed agentName - */ -export type AddResult = Record> = - | ({ success: true; message?: string } & T) - | { success: false; error: string }; +export type { Result }; + +/** @deprecated Use Result directly */ +export type AddResult = Record> = Result; /** * Represents a resource that can be removed. @@ -21,7 +18,7 @@ export interface RemovableResource { /** * Re-export removal types from shared types. */ -export type { RemovalPreview, RemovalResult }; +export type { RemovalPreview }; /** * Screen component type for TUI add flows. diff --git a/src/cli/project.ts b/src/cli/project.ts index c6bf1f5d0..f81ae5426 100644 --- a/src/cli/project.ts +++ b/src/cli/project.ts @@ -18,7 +18,10 @@ export function createDefaultProjectSpec(projectName: string): AgentCoreProjectS onlineEvalConfigs: [], agentCoreGateways: [], policyEngines: [], +<<<<<<< HEAD harnesses: [], +======= +>>>>>>> origin/main configBundles: [], abTests: [], httpGateways: [], diff --git a/src/cli/telemetry/README.md b/src/cli/telemetry/README.md index 7d83ef6dc..00ec3e633 100644 --- a/src/cli/telemetry/README.md +++ b/src/cli/telemetry/README.md @@ -58,6 +58,7 @@ async function withCommandRunTelemetry` (from `src/lib/result.ts`) + +**Behavior:** + +- On success: records `attrs` with `exit_reason: 'success'`, returns the result. +- On failure/throw: records `attrs` with `exit_reason: 'failure'`, returns `{ success: false, error }`. +- If telemetry is unavailable: runs `fn()` untracked. + +Since `attrs` are passed upfront, they are always recorded — even on failure. + +>>>>>>> origin/main **Example with attributes:** ```ts @@ -95,6 +108,25 @@ await runCliCommand('add.widget', !!options.json, async () => { }); ``` +<<<<<<< HEAD +======= +To record attrs on failure, pass `knownAttrs` as the fourth argument: + +```ts +const knownAttrs = { widget_type: standardize(WidgetType, options.type), count: options.items.length }; +await runCliCommand( + 'add.widget', + !!options.json, + async () => { + const result = await widgetPrimitive.add(options); + if (!result.success) throw new Error(result.error); + return knownAttrs; + }, + knownAttrs +); +``` + +>>>>>>> origin/main ## Key Points - Telemetry never crashes the CLI — `standardize()` falls back gracefully, `resilientParse` defaults invalid fields to diff --git a/src/cli/telemetry/__tests__/client.test.ts b/src/cli/telemetry/__tests__/client.test.ts index 96adebafc..cfba2a0c2 100644 --- a/src/cli/telemetry/__tests__/client.test.ts +++ b/src/cli/telemetry/__tests__/client.test.ts @@ -173,5 +173,54 @@ describe('TelemetryClient', () => { exit_reason: 'cancel', }); }); + + it('records fallbackAttrs on failure when provided', async () => { + const sink = new InMemorySink(); + const client = new TelemetryClient(sink); + + await expect( + client.withCommandRun( + 'create', + async () => { + throw new Error('validation failed'); + }, + { + language: 'python', + framework: 'strands', + model_provider: 'bedrock', + memory: 'none', + protocol: 'http', + build: 'codezip', + agent_type: 'create', + network_mode: 'public', + has_agent: true, + } + ) + ).rejects.toThrow('validation failed'); + + expect(sink.metrics).toHaveLength(1); + expect(sink.metrics[0]!.attrs).toMatchObject({ + exit_reason: 'failure', + error_name: 'UnknownError', + language: 'python', + framework: 'strands', + model_provider: 'bedrock', + has_agent: 'true', + }); + }); + + it('records empty attrs on failure when fallbackAttrs not provided', async () => { + const sink = new InMemorySink(); + const client = new TelemetryClient(sink); + + await expect( + client.withCommandRun('deploy', async () => { + throw new Error('boom'); + }) + ).rejects.toThrow('boom'); + + expect(sink.metrics).toHaveLength(1); + expect(sink.metrics[0]!.attrs.language).toBeUndefined(); + }); }); }); diff --git a/src/cli/telemetry/cli-command-run.ts b/src/cli/telemetry/cli-command-run.ts index f04347dc5..f953fc21c 100644 --- a/src/cli/telemetry/cli-command-run.ts +++ b/src/cli/telemetry/cli-command-run.ts @@ -1,9 +1,17 @@ +<<<<<<< HEAD +======= +import type { Result } from '../../lib/result'; +>>>>>>> origin/main import { getErrorMessage } from '../errors'; import { TelemetryClientAccessor } from './client-accessor.js'; import type { Command, CommandAttrs } from './schemas/command-run.js'; +<<<<<<< HEAD // TODO: Replace with a generic Result type that preserves the original error object. export type OperationResult = { success: true } | { success: false; error: string }; +======= +export type OperationResult = Result; +>>>>>>> origin/main async function getTelemetryClient() { try { @@ -31,18 +39,34 @@ export async function withCommandRunTelemetry { result = await fn(); if (!result.success) throw new Error(result.error); return attrs; }); +======= + await client.withCommandRun( + command, + async () => { + result = await fn(); + if (!result.success) throw result.error; + return attrs; + }, + attrs + ); +>>>>>>> origin/main } catch (e) { // withCommandRun re-throws after recording failure telemetry. // If result was set, fn() returned a failure result — return it directly. // If not, fn() itself threw — convert to a failure result so callers // that don't wrap in try/catch (e.g. TUI hooks) don't leak unhandled rejections. if (!result) { +<<<<<<< HEAD return { success: false, error: getErrorMessage(e) } as R; +======= + return { success: false, error: e instanceof Error ? e : new Error(getErrorMessage(e)) } as R; +>>>>>>> origin/main } } return result!; @@ -52,11 +76,20 @@ export async function withCommandRunTelemetry>>>>>> origin/main */ export async function runCliCommand( command: C, json: boolean, +<<<<<<< HEAD fn: () => Promise> +======= + fn: () => Promise>, + knownAttrs?: Partial> +>>>>>>> origin/main ): Promise { try { const client = await getTelemetryClient(); @@ -64,7 +97,11 @@ export async function runCliCommand( await fn(); process.exit(0); } +<<<<<<< HEAD await client.withCommandRun(command, fn); +======= + await client.withCommandRun(command, fn, knownAttrs); +>>>>>>> origin/main process.exit(0); } catch (error) { if (json) { diff --git a/src/cli/telemetry/client.ts b/src/cli/telemetry/client.ts index 91dffd94f..cce94eef2 100644 --- a/src/cli/telemetry/client.ts +++ b/src/cli/telemetry/client.ts @@ -26,7 +26,8 @@ export class TelemetryClient { */ async withCommandRun( command: C, - fn: () => CommandAttrs | typeof CANCELLED | Promise | typeof CANCELLED> + fn: () => CommandAttrs | typeof CANCELLED | Promise | typeof CANCELLED>, + fallbackAttrs?: Partial> ): Promise { const start = performance.now(); try { @@ -43,7 +44,7 @@ export class TelemetryClient { error_name: classifyError(err), is_user_error: isUserError(err), }; - this.recordCommandRun(command, failureResult, {}, Math.round(performance.now() - start)); + this.recordCommandRun(command, failureResult, fallbackAttrs ?? {}, Math.round(performance.now() - start)); throw err; } finally { try { @@ -75,9 +76,14 @@ export class TelemetryClient { // Validate command attrs resiliently: invalid fields default to 'unknown' // instead of dropping the entire metric. +<<<<<<< HEAD // On failure/cancel the callback attrs are empty so validation is skipped. const validatedAttrs = result.exit_reason !== 'failure' && result.exit_reason !== 'cancel' +======= + const validatedAttrs = + Object.keys(attrs as Record).length > 0 +>>>>>>> origin/main ? resilientParse(COMMAND_SCHEMAS[command], attrs as Record) : attrs; diff --git a/src/cli/telemetry/error-classification.ts b/src/cli/telemetry/error-classification.ts index 0cc5d060d..1f5627699 100644 --- a/src/cli/telemetry/error-classification.ts +++ b/src/cli/telemetry/error-classification.ts @@ -9,6 +9,7 @@ const CONFIG_ERRORS = new Set([ 'ConfigReadError', 'ConfigWriteError', 'ConfigParseError', + 'ValidationError', ]); const PACKAGING_ERRORS = new Set([ 'PackagingError', @@ -16,11 +17,13 @@ const PACKAGING_ERRORS = new Set([ 'MissingProjectFileError', 'UnsupportedLanguageError', 'ArtifactSizeError', + 'DependencyCheckError', ]); const CREDENTIAL_ERRORS = new Set([ 'AwsCredentialsError', 'AccessDeniedException', 'AccessDenied', + 'AccessDeniedError', 'ExpiredToken', 'ExpiredTokenException', 'TokenRefreshRequired', @@ -36,6 +39,8 @@ const SERVICE_ERRORS = new Set([ 'ValidationException', 'ConflictException', 'ResourceAlreadyExistsException', + 'ResourceNotFoundError', + 'ConflictError', ]); const USER_CATEGORIES = new Set(['ConfigError', 'CredentialsError', 'ProjectError']); diff --git a/src/cli/telemetry/schemas/__tests__/command-run.test.ts b/src/cli/telemetry/schemas/__tests__/command-run.test.ts index 110df1284..2df12d271 100644 --- a/src/cli/telemetry/schemas/__tests__/command-run.test.ts +++ b/src/cli/telemetry/schemas/__tests__/command-run.test.ts @@ -47,7 +47,7 @@ describe('COMMAND_SCHEMAS', () => { gateway_target_count: 3, policy_engine_count: 0, policy_count: 0, - has_diff: true, + mode: 'diff', }; expect(COMMAND_SCHEMAS.deploy.parse(attrs)).toEqual(attrs); }); @@ -64,7 +64,7 @@ describe('COMMAND_SCHEMAS', () => { gateway_target_count: 0, policy_engine_count: 0, policy_count: 0, - has_diff: false, + mode: 'deploy', }) ).toThrow(); }); @@ -81,7 +81,7 @@ describe('COMMAND_SCHEMAS', () => { gateway_target_count: 0, policy_engine_count: 0, policy_count: 0, - has_diff: false, + mode: 'deploy', }) ).toThrow(); }); diff --git a/src/cli/telemetry/schemas/command-run.ts b/src/cli/telemetry/schemas/command-run.ts index 6386e297d..bf79f42eb 100644 --- a/src/cli/telemetry/schemas/command-run.ts +++ b/src/cli/telemetry/schemas/command-run.ts @@ -100,7 +100,7 @@ const DeployAttrs = safeSchema({ gateway_target_count: Count, policy_engine_count: Count, policy_count: Count, - has_diff: z.boolean(), + mode: z.enum(['deploy', 'dry-run', 'diff']), }); const DevAttrs = safeSchema({ diff --git a/src/cli/tui/hooks/__tests__/useRemove.test.tsx b/src/cli/tui/hooks/__tests__/useRemove.test.tsx index ef2a68dda..e0ed7f9ac 100644 --- a/src/cli/tui/hooks/__tests__/useRemove.test.tsx +++ b/src/cli/tui/hooks/__tests__/useRemove.test.tsx @@ -281,7 +281,7 @@ describe('useRemoveAgent', () => { }); it('calls removeAgent and shows failure result', async () => { - mockAgentRemove.mockResolvedValue({ success: false, error: 'Not found' }); + mockAgentRemove.mockResolvedValue({ success: false, error: new Error('Not found') }); const { lastFrame } = render(); await delay(); diff --git a/src/cli/tui/hooks/useCreateABTest.ts b/src/cli/tui/hooks/useCreateABTest.ts index e54666074..5615d224f 100644 --- a/src/cli/tui/hooks/useCreateABTest.ts +++ b/src/cli/tui/hooks/useCreateABTest.ts @@ -43,7 +43,11 @@ export function useCreateABTest() { enableOnCreate: config.enableOnCreate, }); if (!addResult.success) { +<<<<<<< HEAD throw new Error(addResult.error ?? 'Failed to create AB test'); +======= + throw new Error(addResult.error?.message ?? 'Failed to create AB test'); +>>>>>>> origin/main } setStatus({ state: 'success' }); return { ok: true as const, testName: config.name }; @@ -59,7 +63,11 @@ export function useCreateABTest() { try { const addResult = await abTestPrimitive.addTargetBased(config); if (!addResult.success) { +<<<<<<< HEAD throw new Error(addResult.error ?? 'Failed to create target-based AB test'); +======= + throw new Error(addResult.error?.message ?? 'Failed to create target-based AB test'); +>>>>>>> origin/main } setStatus({ state: 'success' }); return { ok: true as const, testName: config.name }; diff --git a/src/cli/tui/hooks/useCreateConfigBundle.ts b/src/cli/tui/hooks/useCreateConfigBundle.ts index 864501eed..166c92af5 100644 --- a/src/cli/tui/hooks/useCreateConfigBundle.ts +++ b/src/cli/tui/hooks/useCreateConfigBundle.ts @@ -25,7 +25,11 @@ export function useCreateConfigBundle() { commitMessage: config.commitMessage, }); if (!addResult.success) { +<<<<<<< HEAD throw new Error(addResult.error ?? 'Failed to create configuration bundle'); +======= + throw new Error(addResult.error?.message ?? 'Failed to create configuration bundle'); +>>>>>>> origin/main } setStatus({ state: 'success' }); return { ok: true as const, bundleName: config.name }; diff --git a/src/cli/tui/hooks/useCreateEvaluator.ts b/src/cli/tui/hooks/useCreateEvaluator.ts index 087eca479..ebd5818a2 100644 --- a/src/cli/tui/hooks/useCreateEvaluator.ts +++ b/src/cli/tui/hooks/useCreateEvaluator.ts @@ -8,6 +8,7 @@ interface CreateEvaluatorConfig { name: string; level: string; config: EvaluatorConfig; + kmsKeyArn?: string; } export function useCreateEvaluator() { @@ -29,10 +30,14 @@ export function useCreateEvaluator() { name: config.name, level: config.level as 'SESSION' | 'TRACE' | 'TOOL_CALL', config: config.config, +<<<<<<< HEAD +======= + kmsKeyArn: config.kmsKeyArn, +>>>>>>> origin/main }) ); if (!addResult.success) { - throw new Error(addResult.error ?? 'Failed to create evaluator'); + throw new Error(addResult.error?.message ?? 'Failed to create evaluator'); } setStatus({ state: 'success' }); return { ok: true as const, evaluatorName: config.name, codePath: addResult.codePath }; diff --git a/src/cli/tui/hooks/useCreateMcp.ts b/src/cli/tui/hooks/useCreateMcp.ts index 60900bc63..db541c76e 100644 --- a/src/cli/tui/hooks/useCreateMcp.ts +++ b/src/cli/tui/hooks/useCreateMcp.ts @@ -53,7 +53,7 @@ export function useCreateGateway() { }) ); if (!addResult.success) { - throw new Error(addResult.error ?? 'Failed to create gateway'); + throw new Error(addResult.error?.message ?? 'Failed to create gateway'); } const result: CreateGatewayResult = { name: config.name }; setStatus({ state: 'success', result }); diff --git a/src/cli/tui/hooks/useCreateMemory.ts b/src/cli/tui/hooks/useCreateMemory.ts index f125b2615..cf891c4f9 100644 --- a/src/cli/tui/hooks/useCreateMemory.ts +++ b/src/cli/tui/hooks/useCreateMemory.ts @@ -45,7 +45,7 @@ export function useCreateMemory() { }) ); if (!addResult.success) { - throw new Error(addResult.error ?? 'Failed to create memory'); + throw new Error(addResult.error?.message ?? 'Failed to create memory'); } // Read back the memory object const configIO = new ConfigIO(); diff --git a/src/cli/tui/hooks/useCreateOnlineEval.ts b/src/cli/tui/hooks/useCreateOnlineEval.ts index 381e6d136..0fa53f56b 100644 --- a/src/cli/tui/hooks/useCreateOnlineEval.ts +++ b/src/cli/tui/hooks/useCreateOnlineEval.ts @@ -38,7 +38,7 @@ export function useCreateOnlineEval() { }) ); if (!addResult.success) { - throw new Error(addResult.error ?? 'Failed to create online eval config'); + throw new Error(addResult.error?.message ?? 'Failed to create online eval config'); } setStatus({ state: 'success' }); return { ok: true as const, configName: config.name }; diff --git a/src/cli/tui/hooks/useRemove.ts b/src/cli/tui/hooks/useRemove.ts index db1061dd4..549dd628f 100644 --- a/src/cli/tui/hooks/useRemove.ts +++ b/src/cli/tui/hooks/useRemove.ts @@ -1,6 +1,7 @@ +import type { Result } from '../../../lib/result'; import type { ResourceType } from '../../commands/remove/types'; import { RemoveLogger } from '../../logging'; -import type { RemovableGatewayTarget, RemovalPreview, RemovalResult } from '../../operations/remove'; +import type { RemovableGatewayTarget, RemovalPreview } from '../../operations/remove'; import type { RemovableCredential } from '../../primitives/CredentialPrimitive'; import type { RemovableMemory } from '../../primitives/MemoryPrimitive'; import type { RemovablePolicyResource } from '../../primitives/PolicyPrimitive'; @@ -20,6 +21,8 @@ import { policyPrimitive, runtimeEndpointPrimitive, } from '../../primitives/registry'; +import { withCommandRunTelemetry } from '../../telemetry/cli-command-run.js'; +import type { SubCommand } from '../../telemetry/schemas/command-run.js'; import { useCallback, useEffect, useRef, useState } from 'react'; // Re-export types for consumers @@ -61,7 +64,7 @@ function useRemovableResources(loader: () => Promise) { * All useRemove* hooks delegate to this. */ function useRemoveResource( - removeFn: (id: TIdentifier) => Promise, + removeFn: (id: TIdentifier) => Promise, resourceType: ResourceType, getResourceName: (id: TIdentifier) => string ) { @@ -75,7 +78,11 @@ function useRemoveResource( const remove = useCallback(async (id: TIdentifier, preview?: RemovalPreview): Promise => { setState({ isLoading: true, result: null }); - const result = await removeFnRef.current(id); + const result = await withCommandRunTelemetry, Result>( + `remove.${resourceTypeRef.current}`, + {}, + () => removeFnRef.current(id) + ); setState({ isLoading: false, result }); let logPath: string | undefined; @@ -84,7 +91,7 @@ function useRemoveResource( resourceType: resourceTypeRef.current, resourceName: getNameRef.current(id), }); - logger.logRemoval(preview, result.success, result.success ? undefined : result.error); + logger.logRemoval(preview, result.success, result.success ? undefined : result.error?.message); logPath = logger.getAbsoluteLogPath(); setLogFilePath(logPath); } @@ -304,10 +311,10 @@ export function useRemovalPreview() { interface RemovalState { isLoading: boolean; - result: RemovalResult | null; + result: Result | null; } -type RemoveResult = RemovalResult & { logFilePath?: string }; +type RemoveResult = Result & { logFilePath?: string }; export function useRemoveAgent() { return useRemoveResource( diff --git a/src/cli/tui/screens/agent/useAddAgent.ts b/src/cli/tui/screens/agent/useAddAgent.ts index aeac9e442..4b5ccf07a 100644 --- a/src/cli/tui/screens/agent/useAddAgent.ts +++ b/src/cli/tui/screens/agent/useAddAgent.ts @@ -1,4 +1,12 @@ -import { APP_DIR, ConfigIO, NoProjectError, findConfigRoot, setEnvVar } from '../../../../lib'; +import { + APP_DIR, + AgentAlreadyExistsError, + ConfigIO, + NoProjectError, + type Result, + findConfigRoot, + setEnvVar, +} from '../../../../lib'; import type { AgentEnvSpec, DirectoryPath, FilePath } from '../../../../schema'; import { type PythonSetupResult, setupPythonProject } from '../../../operations'; import { createConfigBundleForAgent } from '../../../operations/agent/config-bundle-defaults'; @@ -165,7 +173,11 @@ export function useAddAgent() { () => addAgentInner(config) ); if (!result.success) { +<<<<<<< HEAD return { ok: false, error: result.error }; +======= + return { ok: false, error: result.error.message }; +>>>>>>> origin/main } return result.outcome; } finally { @@ -180,26 +192,42 @@ export function useAddAgent() { return { addAgent, isLoading, reset }; } +<<<<<<< HEAD type AddAgentInnerResult = | { success: true; outcome: AddAgentCreateResult | AddAgentByoResult } | { success: false; error: string }; +======= +type AddAgentInnerResult = Result<{ outcome: AddAgentCreateResult | AddAgentByoResult }>; +>>>>>>> origin/main async function addAgentInner(config: AddAgentConfig): Promise { const configBaseDir = findConfigRoot(); if (!configBaseDir) { +<<<<<<< HEAD return { success: false, error: new NoProjectError().message }; +======= + return { success: false, error: new NoProjectError() }; +>>>>>>> origin/main } const configIO = new ConfigIO({ baseDir: configBaseDir }); if (!configIO.configExists('project')) { +<<<<<<< HEAD return { success: false, error: new NoProjectError().message }; +======= + return { success: false, error: new NoProjectError() }; +>>>>>>> origin/main } const project = await configIO.readProjectSpec(); const existingAgent = project.runtimes.find(agent => agent.name === config.name); if (existingAgent) { +<<<<<<< HEAD return { success: false, error: `Agent "${config.name}" already exists in this project.` }; +======= + return { success: false, error: new AgentAlreadyExistsError(config.name) }; +>>>>>>> origin/main } let outcome: AddAgentCreateResult | AddAgentByoResult | AddAgentError; @@ -212,7 +240,11 @@ async function addAgentInner(config: AddAgentConfig): Promise>>>>>> origin/main } return { success: true, outcome }; } @@ -344,7 +376,7 @@ async function handleImportPath( }); if (!result.success) { - return { ok: false, error: result.error ?? 'Unknown error' }; + return { ok: false, error: result.error?.message ?? 'Unknown error' }; } return { diff --git a/src/cli/tui/screens/create/useCreateFlow.ts b/src/cli/tui/screens/create/useCreateFlow.ts index c95a54fcd..f67e7e671 100644 --- a/src/cli/tui/screens/create/useCreateFlow.ts +++ b/src/cli/tui/screens/create/useCreateFlow.ts @@ -1,4 +1,12 @@ -import { APP_DIR, CONFIG_DIR, ConfigIO, findConfigRoot, setEnvVar, setSessionProjectRoot } from '../../../../lib'; +import { + APP_DIR, + CONFIG_DIR, + ConfigIO, + GitInitError, + findConfigRoot, + setEnvVar, + setSessionProjectRoot, +} from '../../../../lib'; import type { DeployedState } from '../../../../schema'; import { getErrorMessage } from '../../../errors'; import { CreateLogger } from '../../../logging'; @@ -235,7 +243,11 @@ export function useCreateFlow(cwd: string): CreateFlowState { has_agent: addAgentConfig !== null, }; +<<<<<<< HEAD const run = async (): Promise<{ success: true } | { success: false; error: string }> => { +======= + const run = async (): Promise<{ success: true } | { success: false; error: Error }> => { +>>>>>>> origin/main // Project root is now cwd/projectName (creating a new directory) const projectRoot = join(cwd, projectName); const configBaseDir = join(projectRoot, CONFIG_DIR); @@ -298,7 +310,11 @@ export function useCreateFlow(cwd: string): CreateFlowState { logger.endStep('error', errMsg); updateStep(stepIndex, { status: 'error', error: errMsg }); logger.finalize(false); +<<<<<<< HEAD return { success: false, error: errMsg }; +======= + return { success: false, error: new Error(errMsg) }; +>>>>>>> origin/main } // Step: Add agent to project (if addAgentConfig is set) @@ -417,7 +433,7 @@ export function useCreateFlow(cwd: string): CreateFlowState { sessionStorageMountPath: addAgentConfig.sessionStorageMountPath, }); if (!importResult.success) { - throw new Error(importResult.error ?? 'Import failed'); + throw new Error(importResult.error?.message ?? 'Import failed'); } } else { // BYO path: just write config to project (no file generation) @@ -486,7 +502,11 @@ export function useCreateFlow(cwd: string): CreateFlowState { logger.endStep('error', errMsg); updateStep(stepIndex, { status: 'error', error: errMsg }); logger.finalize(false); +<<<<<<< HEAD return { success: false, error: errMsg }; +======= + return { success: false, error: new Error(errMsg) }; +>>>>>>> origin/main } // Step: Set up Python environment (if Python and create path) @@ -588,7 +608,11 @@ export function useCreateFlow(cwd: string): CreateFlowState { logger.endStep('error', errMsg); updateStep(stepIndex, { status: 'error', error: errMsg }); logger.finalize(false); +<<<<<<< HEAD return { success: false, error: errMsg }; +======= + return { success: false, error: new Error(errMsg) }; +>>>>>>> origin/main } // Step: Initialize git repository @@ -600,7 +624,11 @@ export function useCreateFlow(cwd: string): CreateFlowState { logger.endStep('error', gitResult.message); updateStep(stepIndex, { status: 'error', error: gitResult.message }); logger.finalize(false); +<<<<<<< HEAD return { success: false, error: gitResult.message ?? 'Git initialization failed' }; +======= + return { success: false, error: new GitInitError(gitResult.message ?? 'Git initialization failed') }; +>>>>>>> origin/main } else if (gitResult.status === 'skipped') { logger.endStep('warn', gitResult.message); updateStep(stepIndex, { status: 'success', warn: gitResult.message }); @@ -625,7 +653,11 @@ export function useCreateFlow(cwd: string): CreateFlowState { } return prev; }); +<<<<<<< HEAD return { success: false, error: errMsg }; +======= + return { success: false, error: new Error(errMsg) }; +>>>>>>> origin/main } }; diff --git a/src/cli/tui/screens/deploy/useDeployFlow.ts b/src/cli/tui/screens/deploy/useDeployFlow.ts index 33531e521..806b90268 100644 --- a/src/cli/tui/screens/deploy/useDeployFlow.ts +++ b/src/cli/tui/screens/deploy/useDeployFlow.ts @@ -12,11 +12,15 @@ import { parsePolicyEngineOutputs, parsePolicyOutputs, } from '../../../cloudformation'; +import { DEFAULT_DEPLOY_ATTRS, computeDeployAttrs } from '../../../commands/deploy/utils.js'; import { getErrorMessage, isChangesetInProgressError, isExpiredTokenError } from '../../../errors'; import { ExecLogger } from '../../../logging'; import { performStackTeardown, setupTransactionSearch } from '../../../operations/deploy'; import { getGatewayTargetStatuses } from '../../../operations/deploy/gateway-status'; +<<<<<<< HEAD import { createDeploymentManager } from '../../../operations/deploy/imperative'; +======= +>>>>>>> origin/main import { deleteOrphanedABTests, setupABTests } from '../../../operations/deploy/post-deploy-ab-tests'; import { resolveConfigBundleComponentKeys, @@ -24,6 +28,10 @@ import { } from '../../../operations/deploy/post-deploy-config-bundles'; import { setupHttpGateways } from '../../../operations/deploy/post-deploy-http-gateways'; import { enableOnlineEvalConfigs } from '../../../operations/deploy/post-deploy-online-evals'; +<<<<<<< HEAD +======= +import { withCommandRunTelemetry } from '../../../telemetry/cli-command-run.js'; +>>>>>>> origin/main import { type StackDiffSummary, type Step, @@ -315,6 +323,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState setStackOutputs(outputs); const existingState = await configIO.readDeployedState().catch(() => undefined); +<<<<<<< HEAD // Post-CDK: deploy imperative resources (harness) let deployedHarnesses: Record | undefined; @@ -350,6 +359,8 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState // Persist state BEFORE updating React step status — React state updates can // interrupt this async callback by triggering re-renders that dispose resources. +======= +>>>>>>> origin/main let deployedState = buildDeployedState({ targetName: target.name, stackName: currentStackName, @@ -367,6 +378,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState }); await configIO.writeDeployedState(deployedState); +<<<<<<< HEAD // Now safe to update React state — the file write is complete if (imperativeManager.hasDeployersForPhase('post-cdk', imperativeContext)) { if (harnessDeployError) { @@ -380,6 +392,8 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState throw new Error(`Harness deployment failed: ${harnessDeployError}`); } +======= +>>>>>>> origin/main // Post-deploy: Enable online eval configs that have enableOnCreate (CFN deploys them as DISABLED). // Only enable configs that are newly deployed — skip configs that already existed before this // deploy run, so we don't re-enable configs a customer intentionally disabled. @@ -603,7 +617,9 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState if (deployStep.status !== 'pending') return; if (!cdkToolkitWrapper) return; - const run = async () => { + const attrs = context ? computeDeployAttrs(context.projectSpec, 'deploy') : { ...DEFAULT_DEPLOY_ATTRS }; + + const run = async (): Promise<{ success: true } | { success: false; error: Error }> => { // Run diff before deploy to capture pre-deploy differences if (!isDiffRunningRef.current) { isDiffRunningRef.current = true; @@ -705,7 +721,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState if (targetName) { const teardown = await performStackTeardown(targetName); if (!teardown.success) { - throw new Error(`Stack teardown failed: ${teardown.error}`); + throw new Error(`Stack teardown failed: ${teardown.error.message}`); } } } else { @@ -730,8 +746,8 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState agentNames, hasGateways, }); - if (tsResult.error) { - logger.log(`Transaction search setup warning: ${tsResult.error}`, 'warn'); + if (!tsResult.success) { + logger.log(`Transaction search setup warning: ${tsResult.error.message}`, 'warn'); } else { setDeployNotes(prev => [ ...prev, @@ -751,6 +767,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState // Mark both steps as success (in case CFn events were never received) setPublishAssetsStep(prev => ({ ...prev, status: 'success' })); setDeployStep(prev => ({ ...prev, status: 'success' })); + return { success: true } as const; } catch (err) { const errorMsg = getErrorMessage(err); @@ -782,6 +799,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState error: logger.getFailureMessage('Publish assets'), })); } + return { success: false, error: err instanceof Error ? err : new Error(errorMsg) } as const; } finally { // Disable verbose output and clear callback after deploy switchableIoHost?.setVerbose(false); @@ -791,7 +809,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState } }; - void run(); + void withCommandRunTelemetry('deploy', attrs, run); }, [ preflight.phase, cdkToolkitWrapper, @@ -816,7 +834,11 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState if (diffStep.status !== 'pending') return; if (!cdkToolkitWrapper) return; - const run = async () => { + const attrs = context + ? computeDeployAttrs(context.projectSpec, 'diff') + : { ...DEFAULT_DEPLOY_ATTRS, mode: 'diff' as const }; + + const run = async (): Promise<{ success: true } | { success: false; error: Error }> => { setDiffStep(prev => ({ ...prev, status: 'running' })); setShouldStartDeploy(false); setDiffSummaries([]); @@ -837,6 +859,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState logger.endStep('success'); logger.finalize(true); setDiffStep(prev => ({ ...prev, status: 'success' })); + return { success: true }; } catch (err) { const errorMsg = getErrorMessage(err); logger.endStep('error', errorMsg); @@ -851,6 +874,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState status: 'error', error: logger.getFailureMessage('Run CDK diff'), })); + return { success: false, error: err instanceof Error ? err : new Error(errorMsg) }; } finally { switchableIoHost?.setVerbose(false); switchableIoHost?.setOnRawMessage(null); @@ -858,7 +882,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState } }; - void run(); + void withCommandRunTelemetry('deploy', attrs, run); }, [ diffMode, preflight.phase, diff --git a/src/cli/tui/screens/eval/EvalScreen.tsx b/src/cli/tui/screens/eval/EvalScreen.tsx index 3f57999d9..eb33557fc 100644 --- a/src/cli/tui/screens/eval/EvalScreen.tsx +++ b/src/cli/tui/screens/eval/EvalScreen.tsx @@ -339,7 +339,7 @@ export function EvalScreen({ onExit }: EvalScreenProps) { } const result = handleListEvalRuns({}); if (!result.success) { - setState({ phase: 'error', runs: [], error: result.error ?? 'Unknown error' }); + setState({ phase: 'error', runs: [], error: result.error?.message ?? 'Unknown error' }); return; } setState({ phase: 'loaded', runs: result.runs ?? [], error: null }); diff --git a/src/cli/tui/screens/evaluator/AddEvaluatorScreen.tsx b/src/cli/tui/screens/evaluator/AddEvaluatorScreen.tsx index 2c4a2f465..b4294f978 100644 --- a/src/cli/tui/screens/evaluator/AddEvaluatorScreen.tsx +++ b/src/cli/tui/screens/evaluator/AddEvaluatorScreen.tsx @@ -1,5 +1,5 @@ import type { EvaluationLevel, EvaluatorConfig } from '../../../../schema'; -import { EvaluatorNameSchema, isValidBedrockModelId } from '../../../../schema'; +import { EvaluatorNameSchema, isValidBedrockModelId, isValidKmsKeyArn } from '../../../../schema'; import type { SelectableItem } from '../../components'; import { ConfirmReview, Panel, Screen, StepIndicator, TextInput, WizardSelect } from '../../components'; import { HELP_TEXT } from '../../constants'; @@ -91,6 +91,7 @@ export function AddEvaluatorScreen({ onComplete, onExit, existingEvaluatorNames const isRatingScaleCustomStep = wizard.step === 'ratingScale-custom'; const isLambdaArnStep = wizard.step === 'lambda-arn'; const isTimeoutStep = wizard.step === 'timeout'; + const isKmsKeyArnStep = wizard.step === 'kms-key-arn'; const isConfirmStep = wizard.step === 'confirm'; const evaluatorTypeNav = useListNavigation({ @@ -163,6 +164,8 @@ export function AddEvaluatorScreen({ onComplete, onExit, existingEvaluatorNames // Build confirm fields based on evaluator type const confirmFields = useMemo(() => { + const kmsField = wizard.config.kmsKeyArn ? [{ label: 'KMS Key ARN', value: wizard.config.kmsKeyArn }] : []; + if (wizard.evaluatorType === 'llm-as-a-judge') { const llm = wizard.config.config.llmAsAJudge!; return [ @@ -175,6 +178,7 @@ export function AddEvaluatorScreen({ onComplete, onExit, existingEvaluatorNames value: llm.instructions.length > 60 ? llm.instructions.slice(0, 60) + '...' : llm.instructions, }, { label: 'Rating Scale', value: formatRatingScale(llm.ratingScale) }, + ...kmsField, ]; } @@ -187,6 +191,7 @@ export function AddEvaluatorScreen({ onComplete, onExit, existingEvaluatorNames { label: 'Code', value: managed.codeLocation }, { label: 'Entrypoint', value: managed.entrypoint }, { label: 'Timeout', value: `${managed.timeoutSeconds}s` }, + ...kmsField, ]; } @@ -197,6 +202,7 @@ export function AddEvaluatorScreen({ onComplete, onExit, existingEvaluatorNames { label: 'Name', value: wizard.config.name }, { label: 'Level', value: wizard.config.level }, { label: 'Lambda ARN', value: external.lambdaArn }, + ...kmsField, ]; }, [wizard.evaluatorType, wizard.codeBasedType, wizard.config]); @@ -374,6 +380,21 @@ export function AddEvaluatorScreen({ onComplete, onExit, existingEvaluatorNames /> )} + {isKmsKeyArnStep && ( + wizard.goBack()} + customValidation={value => + value === '' || + isValidKmsKeyArn(value) || + 'Must be a valid KMS key ARN (e.g. arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012)' + } + /> + )} + {isConfirmStep && } diff --git a/src/cli/tui/screens/evaluator/types.ts b/src/cli/tui/screens/evaluator/types.ts index 4b19bf85d..229e8b8d5 100644 --- a/src/cli/tui/screens/evaluator/types.ts +++ b/src/cli/tui/screens/evaluator/types.ts @@ -20,12 +20,14 @@ export type AddEvaluatorStep = | 'ratingScale-custom' | 'lambda-arn' | 'timeout' + | 'kms-key-arn' | 'confirm'; export interface AddEvaluatorConfig { name: string; level: EvaluationLevel; config: EvaluatorConfig; + kmsKeyArn?: string; } export const EVALUATOR_STEP_LABELS: Record = { @@ -41,6 +43,7 @@ export const EVALUATOR_STEP_LABELS: Record = { 'ratingScale-custom': 'Scale', 'lambda-arn': 'Lambda', timeout: 'Timeout', + 'kms-key-arn': 'KMS Key', confirm: 'Confirm', }; diff --git a/src/cli/tui/screens/evaluator/useAddEvaluatorWizard.ts b/src/cli/tui/screens/evaluator/useAddEvaluatorWizard.ts index 1bc54f50b..ff31f2009 100644 --- a/src/cli/tui/screens/evaluator/useAddEvaluatorWizard.ts +++ b/src/cli/tui/screens/evaluator/useAddEvaluatorWizard.ts @@ -22,6 +22,7 @@ const LLM_STEPS: AddEvaluatorStep[] = [ 'model', 'instructions', 'ratingScale', + 'kms-key-arn', 'confirm', ]; const CODE_MANAGED_STEPS: AddEvaluatorStep[] = [ @@ -30,6 +31,7 @@ const CODE_MANAGED_STEPS: AddEvaluatorStep[] = [ 'name', 'level', 'timeout', + 'kms-key-arn', 'confirm', ]; const CODE_EXTERNAL_STEPS: AddEvaluatorStep[] = [ @@ -38,6 +40,7 @@ const CODE_EXTERNAL_STEPS: AddEvaluatorStep[] = [ 'name', 'level', 'lambda-arn', + 'kms-key-arn', 'confirm', ]; @@ -80,6 +83,7 @@ export function useAddEvaluatorWizard() { const [lambdaArn, setLambdaArnState] = useState(''); const [timeout, setTimeoutState] = useState(DEFAULT_CODE_TIMEOUT); const [customRatingScaleType, setCustomRatingScaleType] = useState('numerical'); + const [kmsKeyArn, setKmsKeyArnState] = useState(''); const [step, setStep] = useState('evaluator-type'); const steps = useMemo(() => getSteps(evaluatorType, codeBasedType), [evaluatorType, codeBasedType]); @@ -109,11 +113,13 @@ export function useAddEvaluatorWizard() { // Build the final config based on current state const config: AddEvaluatorConfig = useMemo(() => { + const kms = kmsKeyArn || undefined; if (evaluatorType === 'llm-as-a-judge') { return { name, level, config: { llmAsAJudge: llmConfig }, + ...(kms && { kmsKeyArn: kms }), }; } @@ -126,6 +132,7 @@ export function useAddEvaluatorWizard() { external: { lambdaArn }, }, }, + ...(kms && { kmsKeyArn: kms }), }; } @@ -143,8 +150,9 @@ export function useAddEvaluatorWizard() { }, }, }, + ...(kms && { kmsKeyArn: kms }), }; - }, [evaluatorType, codeBasedType, name, level, llmConfig, lambdaArn, timeout]); + }, [evaluatorType, codeBasedType, name, level, llmConfig, lambdaArn, timeout, kmsKeyArn]); const selectEvaluatorType = useCallback((type: EvaluatorTypeId) => { setEvaluatorType(type); @@ -256,6 +264,15 @@ export function useAddEvaluatorWizard() { [nextStep] ); + const setKmsKeyArn = useCallback( + (arn: string) => { + setKmsKeyArnState(arn); + const next = nextStep('kms-key-arn'); + if (next) setStep(next); + }, + [nextStep] + ); + const reset = useCallback(() => { setEvaluatorType('code-based'); setCodeBasedType('managed'); @@ -264,6 +281,7 @@ export function useAddEvaluatorWizard() { setLlmConfig(getDefaultLlmConfig().llmAsAJudge!); setLambdaArnState(''); setTimeoutState(DEFAULT_CODE_TIMEOUT); + setKmsKeyArnState(''); setStep('evaluator-type'); }, []); @@ -288,6 +306,7 @@ export function useAddEvaluatorWizard() { setCustomRatingScale, setLambdaArn, setTimeout, + setKmsKeyArn, reset, }; } diff --git a/src/cli/tui/screens/identity/useCreateIdentity.ts b/src/cli/tui/screens/identity/useCreateIdentity.ts index c9293665b..031f30d42 100644 --- a/src/cli/tui/screens/identity/useCreateIdentity.ts +++ b/src/cli/tui/screens/identity/useCreateIdentity.ts @@ -25,7 +25,7 @@ export function useCreateIdentity() { () => credentialPrimitive.add(config) ); if (!result.success) { - throw new Error(result.error ?? 'Failed to create credential'); + throw new Error(result.error?.message ?? 'Failed to create credential'); } // Read back the credential object const configIO = new ConfigIO(); diff --git a/src/cli/tui/screens/import/ImportFlow.tsx b/src/cli/tui/screens/import/ImportFlow.tsx index ed82962d3..0a0843f7e 100644 --- a/src/cli/tui/screens/import/ImportFlow.tsx +++ b/src/cli/tui/screens/import/ImportFlow.tsx @@ -151,7 +151,7 @@ export function ImportFlow({ onBack, onNavigate }: ImportFlowProps) { Name: {result.resourceName} - {result.resourceId && ( + {'resourceId' in result && result.resourceId && ( ID: {result.resourceId} diff --git a/src/cli/tui/screens/import/ImportProgressScreen.tsx b/src/cli/tui/screens/import/ImportProgressScreen.tsx index 3771ffbab..a0f0229cf 100644 --- a/src/cli/tui/screens/import/ImportProgressScreen.tsx +++ b/src/cli/tui/screens/import/ImportProgressScreen.tsx @@ -59,9 +59,9 @@ export function ImportProgressScreen({ onSuccess(result); } else { setSteps(prev => - prev.map(s => (s.status === 'running' ? { ...s, status: 'error', error: result.error } : s)) + prev.map(s => (s.status === 'running' ? { ...s, status: 'error', error: result.error.message } : s)) ); - onError(result.error ?? 'Import failed'); + onError(result.error.message ?? 'Import failed'); } } else { // Starter toolkit @@ -75,9 +75,9 @@ export function ImportProgressScreen({ onSuccess(result); } else { setSteps(prev => - prev.map(s => (s.status === 'running' ? { ...s, status: 'error', error: result.error } : s)) + prev.map(s => (s.status === 'running' ? { ...s, status: 'error', error: result.error.message } : s)) ); - onError(result.error ?? 'Import failed'); + onError(result.error.message ?? 'Import failed'); } } }; diff --git a/src/cli/tui/screens/invoke/useInvokeFlow.ts b/src/cli/tui/screens/invoke/useInvokeFlow.ts index 1f872370b..4f631901b 100644 --- a/src/cli/tui/screens/invoke/useInvokeFlow.ts +++ b/src/cli/tui/screens/invoke/useInvokeFlow.ts @@ -50,11 +50,14 @@ export interface InvokeConfig { protocol?: ProtocolMode; authorizerType?: RuntimeAuthorizerType; baggage?: string; +<<<<<<< HEAD }[]; harnesses: { name: string; state: HarnessDeployedState; authorizerType?: RuntimeAuthorizerType; +======= +>>>>>>> origin/main }[]; target: AwsDeploymentTarget; targetName: string; diff --git a/src/cli/tui/screens/online-eval/OnlineEvalDashboard.tsx b/src/cli/tui/screens/online-eval/OnlineEvalDashboard.tsx index 03961473e..bfc0e7ed1 100644 --- a/src/cli/tui/screens/online-eval/OnlineEvalDashboard.tsx +++ b/src/cli/tui/screens/online-eval/OnlineEvalDashboard.tsx @@ -156,7 +156,7 @@ export function OnlineEvalDashboard({ onExit }: OnlineEvalDashboardProps) { setState(prev => ({ ...prev, phase: 'toggling' })); void handlePauseResume({ name: item.name }, action).then(result => { if (!result.success) { - setState(prev => ({ ...prev, phase: 'loaded', error: result.error ?? 'Toggle failed' })); + setState(prev => ({ ...prev, phase: 'loaded', error: result.error?.message ?? 'Toggle failed' })); return; } return fetchDashboardConfigs().then(configs => { diff --git a/src/cli/tui/screens/policy/AddPolicyFlow.tsx b/src/cli/tui/screens/policy/AddPolicyFlow.tsx index 8238d84c8..54a180dd0 100644 --- a/src/cli/tui/screens/policy/AddPolicyFlow.tsx +++ b/src/cli/tui/screens/policy/AddPolicyFlow.tsx @@ -139,7 +139,7 @@ export function AddPolicyFlow({ isInteractive = true, onExit, onBack, onDev, onD () => policyEnginePrimitive.add({ name: engineName }) ); if (!result.success) { - setFlow({ name: 'error', message: result.error }); + setFlow({ name: 'error', message: result.error.message }); return; } setEngineNames(prev => [...prev, engineName]); @@ -184,7 +184,7 @@ export function AddPolicyFlow({ isInteractive = true, onExit, onBack, onDev, onD setPolicyNames(prev => [...prev, config.name]); setFlow({ name: 'policy-success', policyName: config.name, engineName: config.engine }); } else { - setFlow({ name: 'error', message: result.error }); + setFlow({ name: 'error', message: result.error.message }); } }, []); diff --git a/src/cli/tui/screens/recommendation/RecommendationFlow.tsx b/src/cli/tui/screens/recommendation/RecommendationFlow.tsx index b28344df3..fc1d3fbab 100644 --- a/src/cli/tui/screens/recommendation/RecommendationFlow.tsx +++ b/src/cli/tui/screens/recommendation/RecommendationFlow.tsx @@ -33,7 +33,16 @@ type FlowState = recommendationId?: string; region?: string; } +<<<<<<< HEAD | { name: 'results'; result: RunRecommendationCommandResult; config: RecommendationWizardConfig; filePath?: string } +======= + | { + name: 'results'; + result: Extract; + config: RecommendationWizardConfig; + filePath?: string; + } +>>>>>>> origin/main | { name: 'creds-error'; message: string } | { name: 'error'; message: string; logFilePath?: string }; @@ -194,13 +203,25 @@ export function RecommendationFlow({ onExit }: RecommendationFlowProps) { setFlow(prev => { if (prev.name !== 'running') return prev; const steps = prev.steps.map(s => +<<<<<<< HEAD s.status === 'running' ? { ...s, status: 'error' as const, error: result.error } : s +======= + s.status === 'running' ? { ...s, status: 'error' as const, error: result.error.message } : s +>>>>>>> origin/main ); return { ...prev, steps }; }); await new Promise(resolve => setTimeout(resolve, 2000)); if (cancelled) return; +<<<<<<< HEAD setFlow({ name: 'error', message: result.error ?? 'Recommendation failed', logFilePath: result.logFilePath }); +======= + setFlow({ + name: 'error', + message: result.error?.message ?? 'Recommendation failed', + logFilePath: result.logFilePath, + }); +>>>>>>> origin/main return; } @@ -333,7 +354,11 @@ export function RecommendationFlow({ onExit }: RecommendationFlowProps) { // ───────────────────────────────────────────────────────────────────────────── interface ResultsViewProps { +<<<<<<< HEAD result: RunRecommendationCommandResult; +======= + result: Extract; +>>>>>>> origin/main config: RecommendationWizardConfig; filePath?: string; onRunAnother: () => void; @@ -369,7 +394,11 @@ function ResultsView({ result, config, filePath, onRunAnother, onExit }: Results message: `New bundle version (${applyResult.newVersionId}) created with recommended changes. Local config updated.`, }); } else { +<<<<<<< HEAD setApplyStatus({ applied: false, message: applyResult.error ?? 'Unknown error' }); +======= + setApplyStatus({ applied: false, message: applyResult.error?.message ?? 'Unknown error' }); +>>>>>>> origin/main } } catch (err) { setApplyStatus({ applied: false, message: getErrorMessage(err) }); diff --git a/src/cli/tui/screens/remove/RemoveFlow.tsx b/src/cli/tui/screens/remove/RemoveFlow.tsx index 6ad51fb47..89f43bf99 100644 --- a/src/cli/tui/screens/remove/RemoveFlow.tsx +++ b/src/cli/tui/screens/remove/RemoveFlow.tsx @@ -61,8 +61,11 @@ type FlowState = | { name: 'select-online-eval' } | { name: 'select-policy-engine' } | { name: 'select-policy' } +<<<<<<< HEAD | { name: 'select-harness' } | { name: 'confirm-harness'; harnessName: string; preview: RemovalPreview } +======= +>>>>>>> origin/main | { name: 'select-config-bundle' } | { name: 'select-ab-test' } | { name: 'select-runtime-endpoint' } @@ -347,7 +350,7 @@ export function RemoveFlow({ if (removeResult.success) { setFlow({ name: 'agent-success', agentName }); } else { - setFlow({ name: 'error', message: removeResult.error }); + setFlow({ name: 'error', message: removeResult.error.message }); } } else { setFlow({ name: 'confirm-agent', agentName, preview: result.preview }); @@ -391,7 +394,7 @@ export function RemoveFlow({ if (removeResult.success) { setFlow({ name: 'gateway-success', gatewayName }); } else { - setFlow({ name: 'error', message: removeResult.error }); + setFlow({ name: 'error', message: removeResult.error.message }); } } else { setFlow({ name: 'confirm-gateway', gatewayName, preview: result.preview }); @@ -413,7 +416,7 @@ export function RemoveFlow({ if (removeResult.success) { setFlow({ name: 'tool-success', toolName: tool.name }); } else { - setFlow({ name: 'error', message: removeResult.error }); + setFlow({ name: 'error', message: removeResult.error.message }); } } else { setFlow({ name: 'confirm-gateway-target', tool, preview: result.preview }); @@ -435,7 +438,7 @@ export function RemoveFlow({ if (removeResult.success) { setFlow({ name: 'memory-success', memoryName }); } else { - setFlow({ name: 'error', message: removeResult.error }); + setFlow({ name: 'error', message: removeResult.error.message }); } } else { setFlow({ name: 'confirm-memory', memoryName, preview: result.preview }); @@ -457,7 +460,7 @@ export function RemoveFlow({ if (removeResult.success) { setFlow({ name: 'identity-success', identityName }); } else { - setFlow({ name: 'error', message: removeResult.error }); + setFlow({ name: 'error', message: removeResult.error.message }); } } else { setFlow({ name: 'confirm-identity', identityName, preview: result.preview }); @@ -479,7 +482,7 @@ export function RemoveFlow({ if (removeResult.success) { setFlow({ name: 'evaluator-success', evaluatorName }); } else { - setFlow({ name: 'error', message: removeResult.error }); + setFlow({ name: 'error', message: removeResult.error.message }); } } else { setFlow({ name: 'confirm-evaluator', evaluatorName, preview: result.preview }); @@ -501,7 +504,7 @@ export function RemoveFlow({ if (removeResult.success) { setFlow({ name: 'online-eval-success', configName }); } else { - setFlow({ name: 'error', message: removeResult.error }); + setFlow({ name: 'error', message: removeResult.error.message }); } } else { setFlow({ name: 'confirm-online-eval', configName, preview: result.preview }); @@ -523,7 +526,7 @@ export function RemoveFlow({ if (removeResult.success) { setFlow({ name: 'policy-engine-success', engineName }); } else { - setFlow({ name: 'error', message: removeResult.error }); + setFlow({ name: 'error', message: removeResult.error.message }); } } else { setFlow({ name: 'confirm-policy-engine', engineName, preview: result.preview }); @@ -548,7 +551,7 @@ export function RemoveFlow({ if (removeResult.success) { setFlow({ name: 'policy-success', policyName }); } else { - setFlow({ name: 'error', message: removeResult.error }); + setFlow({ name: 'error', message: removeResult.error.message }); } } else { setFlow({ name: 'confirm-policy', compositeKey, policyName, preview: result.preview }); @@ -570,7 +573,11 @@ export function RemoveFlow({ if (removeResult.success) { setFlow({ name: 'config-bundle-success', bundleName }); } else { +<<<<<<< HEAD setFlow({ name: 'error', message: removeResult.error }); +======= + setFlow({ name: 'error', message: removeResult.error.message }); +>>>>>>> origin/main } } else { setFlow({ name: 'confirm-config-bundle', bundleName, preview: result.preview }); @@ -592,7 +599,11 @@ export function RemoveFlow({ if (removeResult.success) { setFlow({ name: 'ab-test-success', testName }); } else { +<<<<<<< HEAD setFlow({ name: 'error', message: removeResult.error }); +======= + setFlow({ name: 'error', message: removeResult.error.message }); +>>>>>>> origin/main } } else { setFlow({ name: 'confirm-ab-test', testName, preview: result.preview }); @@ -614,7 +625,7 @@ export function RemoveFlow({ if (removeResult.success) { setFlow({ name: 'runtime-endpoint-success', endpointName }); } else { - setFlow({ name: 'error', message: removeResult.error }); + setFlow({ name: 'error', message: removeResult.error.message }); } } else { setFlow({ name: 'confirm-runtime-endpoint', endpointName, preview: result.preview }); @@ -700,7 +711,7 @@ export function RemoveFlow({ if (result.success) { pendingResultRef.current = { name: 'agent-success', agentName, logFilePath: result.logFilePath }; } else { - pendingResultRef.current = { name: 'error', message: result.error }; + pendingResultRef.current = { name: 'error', message: result.error.message }; } setResultReady(true); }, @@ -732,7 +743,7 @@ export function RemoveFlow({ if (result.success) { pendingResultRef.current = { name: 'gateway-success', gatewayName, logFilePath: result.logFilePath }; } else { - pendingResultRef.current = { name: 'error', message: result.error }; + pendingResultRef.current = { name: 'error', message: result.error.message }; } setResultReady(true); }, @@ -748,7 +759,7 @@ export function RemoveFlow({ if (result.success) { pendingResultRef.current = { name: 'tool-success', toolName: tool.name, logFilePath: result.logFilePath }; } else { - pendingResultRef.current = { name: 'error', message: result.error }; + pendingResultRef.current = { name: 'error', message: result.error.message }; } setResultReady(true); }, @@ -764,7 +775,7 @@ export function RemoveFlow({ if (result.success) { pendingResultRef.current = { name: 'memory-success', memoryName, logFilePath: result.logFilePath }; } else { - pendingResultRef.current = { name: 'error', message: result.error }; + pendingResultRef.current = { name: 'error', message: result.error.message }; } setResultReady(true); }, @@ -780,7 +791,7 @@ export function RemoveFlow({ if (result.success) { pendingResultRef.current = { name: 'identity-success', identityName, logFilePath: result.logFilePath }; } else { - pendingResultRef.current = { name: 'error', message: result.error }; + pendingResultRef.current = { name: 'error', message: result.error.message }; } setResultReady(true); }, @@ -796,7 +807,7 @@ export function RemoveFlow({ if (result.success) { pendingResultRef.current = { name: 'evaluator-success', evaluatorName, logFilePath: result.logFilePath }; } else { - pendingResultRef.current = { name: 'error', message: result.error }; + pendingResultRef.current = { name: 'error', message: result.error.message }; } setResultReady(true); }, @@ -812,7 +823,7 @@ export function RemoveFlow({ if (result.success) { pendingResultRef.current = { name: 'online-eval-success', configName, logFilePath: result.logFilePath }; } else { - pendingResultRef.current = { name: 'error', message: result.error }; + pendingResultRef.current = { name: 'error', message: result.error.message }; } setResultReady(true); }, @@ -828,7 +839,7 @@ export function RemoveFlow({ if (result.success) { pendingResultRef.current = { name: 'policy-engine-success', engineName, logFilePath: result.logFilePath }; } else { - pendingResultRef.current = { name: 'error', message: result.error }; + pendingResultRef.current = { name: 'error', message: result.error.message }; } setResultReady(true); }, @@ -844,7 +855,7 @@ export function RemoveFlow({ if (result.success) { pendingResultRef.current = { name: 'policy-success', policyName, logFilePath: result.logFilePath }; } else { - pendingResultRef.current = { name: 'error', message: result.error }; + pendingResultRef.current = { name: 'error', message: result.error.message }; } setResultReady(true); }, @@ -860,7 +871,11 @@ export function RemoveFlow({ if (result.success) { pendingResultRef.current = { name: 'config-bundle-success', bundleName, logFilePath: result.logFilePath }; } else { +<<<<<<< HEAD pendingResultRef.current = { name: 'error', message: result.error }; +======= + pendingResultRef.current = { name: 'error', message: result.error.message }; +>>>>>>> origin/main } setResultReady(true); }, @@ -876,7 +891,11 @@ export function RemoveFlow({ if (result.success) { pendingResultRef.current = { name: 'ab-test-success', testName, logFilePath: result.logFilePath }; } else { +<<<<<<< HEAD pendingResultRef.current = { name: 'error', message: result.error }; +======= + pendingResultRef.current = { name: 'error', message: result.error.message }; +>>>>>>> origin/main } setResultReady(true); }, @@ -892,7 +911,7 @@ export function RemoveFlow({ if (result.success) { pendingResultRef.current = { name: 'runtime-endpoint-success', endpointName, logFilePath: result.logFilePath }; } else { - pendingResultRef.current = { name: 'error', message: result.error }; + pendingResultRef.current = { name: 'error', message: result.error.message }; } setResultReady(true); }, diff --git a/src/cli/tui/screens/remove/__tests__/RemoveScreen.test.tsx b/src/cli/tui/screens/remove/__tests__/RemoveScreen.test.tsx index fc386eece..d105c1e6d 100644 --- a/src/cli/tui/screens/remove/__tests__/RemoveScreen.test.tsx +++ b/src/cli/tui/screens/remove/__tests__/RemoveScreen.test.tsx @@ -88,7 +88,10 @@ describe('RemoveScreen', () => { configBundleCount={0} abTestCount={2} runtimeEndpointCount={0} +<<<<<<< HEAD harnessCount={0} +======= +>>>>>>> origin/main /> ); @@ -116,7 +119,10 @@ describe('RemoveScreen', () => { configBundleCount={0} abTestCount={0} runtimeEndpointCount={0} +<<<<<<< HEAD harnessCount={0} +======= +>>>>>>> origin/main /> ); diff --git a/src/cli/tui/screens/remove/useRemoveFlow.ts b/src/cli/tui/screens/remove/useRemoveFlow.ts index 96ddd2f64..5bbc5f1fb 100644 --- a/src/cli/tui/screens/remove/useRemoveFlow.ts +++ b/src/cli/tui/screens/remove/useRemoveFlow.ts @@ -1,4 +1,5 @@ import { ConfigIO, getWorkingDirectory } from '../../../../lib'; +import type { Result } from '../../../../lib/result'; import { findStack } from '../../../cloudformation/stack-discovery'; import { getErrorMessage } from '../../../errors'; import type { RemovalResult } from '../../../operations/remove/types'; @@ -151,7 +152,11 @@ export function useRemoveFlow({ force, dryRun }: RemoveFlowOptions): RemoveFlowS const res = await withCommandRunTelemetry( 'remove.all', {}, +<<<<<<< HEAD (): Promise => +======= + (): Promise => +>>>>>>> origin/main withMinDuration(async () => { const configIO = new ConfigIO(); @@ -164,7 +169,11 @@ export function useRemoveFlow({ force, dryRun }: RemoveFlowOptions): RemoveFlowS return { success: true }; }) ); +<<<<<<< HEAD if (!res.success) throw new Error(res.error); +======= + if (!res.success) throw res.error; +>>>>>>> origin/main updateStep(0, { status: 'success' }); } catch (err) { updateStep(0, { status: 'error', error: getErrorMessage(err) }); diff --git a/src/cli/tui/screens/run-eval/RunBatchEvalFlow.tsx b/src/cli/tui/screens/run-eval/RunBatchEvalFlow.tsx index 38c03c150..08a1ef291 100644 --- a/src/cli/tui/screens/run-eval/RunBatchEvalFlow.tsx +++ b/src/cli/tui/screens/run-eval/RunBatchEvalFlow.tsx @@ -260,7 +260,11 @@ export function RunBatchEvalFlow({ onExit }: RunBatchEvalFlowProps) { setFlow(prev => { if (prev.name !== 'running') return prev; const steps = prev.steps.map(s => +<<<<<<< HEAD s.status === 'running' ? { ...s, status: 'error' as const, error: result.error } : s +======= + s.status === 'running' ? { ...s, status: 'error' as const, error: result.error.message } : s +>>>>>>> origin/main ); return { ...prev, steps }; }); @@ -268,7 +272,11 @@ export function RunBatchEvalFlow({ onExit }: RunBatchEvalFlowProps) { if (cancelled) return; setFlow({ name: 'error', +<<<<<<< HEAD message: result.error ?? 'Batch evaluation failed', +======= + message: result.error?.message ?? 'Batch evaluation failed', +>>>>>>> origin/main logFilePath: result.logFilePath, }); return; diff --git a/src/cli/tui/screens/run-eval/RunEvalFlow.tsx b/src/cli/tui/screens/run-eval/RunEvalFlow.tsx index 6b4ced516..231589b2d 100644 --- a/src/cli/tui/screens/run-eval/RunEvalFlow.tsx +++ b/src/cli/tui/screens/run-eval/RunEvalFlow.tsx @@ -20,7 +20,7 @@ type FlowState = | { name: 'loading' } | { name: 'wizard'; data: RunEvalFlowData } | { name: 'running'; config: RunEvalConfig } - | { name: 'results'; result: RunEvalResult; run: EvalRunResult } + | { name: 'results'; result: RunEvalResult; run: EvalRunResult; filePath: string } | { name: 'creds-error'; message: string } | { name: 'error'; message: string }; @@ -145,12 +145,12 @@ export function RunEvalFlow({ onExit, onViewRuns }: RunEvalFlowProps) { if (cancelled) return; - if (!result.success || !result.run) { - setFlow({ name: 'error', message: result.error ?? 'Evaluation failed' }); + if (!result.success) { + setFlow({ name: 'error', message: result.error.message }); return; } - setFlow({ name: 'results', result, run: result.run }); + setFlow({ name: 'results', result, run: result.run, filePath: result.filePath }); } catch (err) { if (!cancelled) setFlow({ name: 'error', message: getErrorMessage(err) }); } @@ -196,7 +196,7 @@ export function RunEvalFlow({ onExit, onViewRuns }: RunEvalFlowProps) { return ( setFlow({ name: 'loading' })} onViewRuns={onViewRuns} onExit={onExit} diff --git a/src/cli/tui/screens/runtime-endpoint/AddRuntimeEndpointFlow.tsx b/src/cli/tui/screens/runtime-endpoint/AddRuntimeEndpointFlow.tsx index 77c07b2e8..f7d5869e6 100644 --- a/src/cli/tui/screens/runtime-endpoint/AddRuntimeEndpointFlow.tsx +++ b/src/cli/tui/screens/runtime-endpoint/AddRuntimeEndpointFlow.tsx @@ -95,7 +95,11 @@ export function AddRuntimeEndpointFlow({ }); return; } +<<<<<<< HEAD setFlow({ name: 'error', message: result.error ?? 'Unknown error' }); +======= + setFlow({ name: 'error', message: result.error?.message ?? 'Unknown error' }); +>>>>>>> origin/main }); }, []); diff --git a/src/cli/tui/screens/status/useStatusFlow.ts b/src/cli/tui/screens/status/useStatusFlow.ts index 2260e1e82..d54ea6e79 100644 --- a/src/cli/tui/screens/status/useStatusFlow.ts +++ b/src/cli/tui/screens/status/useStatusFlow.ts @@ -99,7 +99,7 @@ export function useStatusFlow() { ...prev, phase: 'ready', statusesLoaded: true, - statusesError: result.error, + statusesError: result.error?.message, })); return; } diff --git a/src/lib/errors/index.ts b/src/lib/errors/index.ts index f03c2281a..2acd167c8 100644 --- a/src/lib/errors/index.ts +++ b/src/lib/errors/index.ts @@ -1 +1,2 @@ export * from './config'; +export * from './types'; diff --git a/src/lib/errors/types.ts b/src/lib/errors/types.ts new file mode 100644 index 000000000..f0a05106c --- /dev/null +++ b/src/lib/errors/types.ts @@ -0,0 +1,94 @@ +/** + * Error indicating no agentcore project was found in the working directory. + */ +export class NoProjectError extends Error { + constructor(message?: string) { + super(message ?? 'No agentcore project found. Run "agentcore create" first.'); + this.name = 'NoProjectError'; + } +} + +/** + * Error thrown when an agent with the same name already exists. + */ +export class AgentAlreadyExistsError extends Error { + constructor(agentName: string) { + super(`An agent named "${agentName}" already exists in the schema.`); + this.name = 'AgentAlreadyExistsError'; + } +} + +/** + * Error indicating an AWS permissions failure (AccessDenied / AccessDeniedException). + */ +export class AccessDeniedError extends Error { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = 'AccessDeniedError'; + } +} + +/** + * Error indicating missing system dependencies required for an operation. + */ +export class DependencyCheckError extends Error { + constructor(errors: string[]) { + super(errors.join('\n')); + this.name = 'DependencyCheckError'; + } +} + +/** + * Error indicating git repository initialization failed. + */ +export class GitInitError extends Error { + constructor(message: string) { + super(message); + this.name = 'GitInitError'; + } +} + +/** + * Error indicating a referenced resource could not be found. + */ +export class ResourceNotFoundError extends Error { + constructor(message: string) { + super(message); + this.name = 'ResourceNotFoundError'; + } +} + +export class ValidationError extends Error { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = 'ValidationError'; + } +} + +/** + * Converts an unknown thrown value to an Error instance. + * Use in catch blocks to ensure the error field is always an Error object. + */ +export function toError(err: unknown): Error { + return err instanceof Error ? err : new Error(String(err)); +} + +/** + * Error indicating a resource conflict (e.g., already exists). + */ +export class ConflictError extends Error { + constructor(message: string) { + super(message); + this.name = 'ConflictError'; + } +} + +/** + * Error indicating an operation timed out or exceeded retry limits. + */ +export class TimeoutError extends Error { + constructor(message: string) { + super(message); + this.name = 'TimeoutError'; + } +} diff --git a/src/lib/index.ts b/src/lib/index.ts index d21baec86..b261504af 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -28,3 +28,8 @@ export * from './utils'; // Schema I/O utilities export * from './schemas/io'; export * from './time-constants'; +<<<<<<< HEAD +======= +export { serializeResult } from './result'; +export type { Result } from './result'; +>>>>>>> origin/main diff --git a/src/lib/result.test.ts b/src/lib/result.test.ts new file mode 100644 index 000000000..9ecdd2bad --- /dev/null +++ b/src/lib/result.test.ts @@ -0,0 +1,19 @@ +import { serializeResult } from './result'; +import { describe, expect, it } from 'vitest'; + +describe('serializeResult', () => { + it('passes through success results unchanged', () => { + const result = { success: true as const, name: 'test' }; + expect(serializeResult(result)).toEqual({ success: true, name: 'test' }); + }); + + it('converts error.message to string on failure', () => { + const result = { success: false as const, error: new Error('something broke') }; + expect(serializeResult(result)).toEqual({ success: false, error: 'something broke' }); + }); + + it('preserves extra fields on failure branch', () => { + const result = { success: false as const, error: new Error('fail'), logPath: '/tmp/log' }; + expect(serializeResult(result)).toEqual({ success: false, error: 'fail', logPath: '/tmp/log' }); + }); +}); diff --git a/src/lib/result.ts b/src/lib/result.ts new file mode 100644 index 000000000..abec578c3 --- /dev/null +++ b/src/lib/result.ts @@ -0,0 +1,30 @@ +/** + * Discriminated union for fallible operations, inspired by Rust's Result. + * + * Success branch spreads T onto the result; failure branch carries an Error. + * E extends Error so callers always get stack traces, cause chains, and instanceof narrowing. + * + * @example + * Result // { success: true } | { success: false; error: Error } + * Result<{ name: string }> // { success: true; name: string } | { success: false; error: Error } + * Result<{ name: string }, ValidationError> // { success: true; name: string } | { success: false; error: ValidationError } + */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export type Result = {}, E extends Error = Error> = + | ({ success: true } & T) + | { success: false; error: E }; + +/** + * Converts a Result object to a JSON-serializable form. + * Error objects have non-enumerable properties, so JSON.stringify produces `{}`. + * This replaces the `error` field with the error message string. + */ +export function serializeResult>( + result: ({ success: true } & T) | ({ success: false; error: Error } & Record) +): Record { + if (!result.success) { + const { error, ...rest } = result; + return { ...rest, error: error.message }; + } + return result; +} diff --git a/src/lib/schemas/io/__tests__/config-io.test.ts b/src/lib/schemas/io/__tests__/config-io.test.ts index ffe829fa0..ffb5e3c29 100644 --- a/src/lib/schemas/io/__tests__/config-io.test.ts +++ b/src/lib/schemas/io/__tests__/config-io.test.ts @@ -1,6 +1,6 @@ +import { NoProjectError } from '../../../errors'; import { ConfigNotFoundError, ConfigParseError, ConfigValidationError } from '../../../errors/config.js'; import { ConfigIO } from '../config-io.js'; -import { NoProjectError } from '../path-resolver.js'; import { randomUUID } from 'node:crypto'; import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; import { mkdir, rm, unlink, writeFile } from 'node:fs/promises'; diff --git a/src/lib/schemas/io/__tests__/path-resolver.test.ts b/src/lib/schemas/io/__tests__/path-resolver.test.ts index 70bff97e2..320d797fd 100644 --- a/src/lib/schemas/io/__tests__/path-resolver.test.ts +++ b/src/lib/schemas/io/__tests__/path-resolver.test.ts @@ -1,5 +1,5 @@ +import { NoProjectError } from '../../../errors'; import { - NoProjectError, PathResolver, findConfigRoot, findProjectRoot, diff --git a/src/lib/schemas/io/config-io.ts b/src/lib/schemas/io/config-io.ts index e4b2740e4..9e954593e 100644 --- a/src/lib/schemas/io/config-io.ts +++ b/src/lib/schemas/io/config-io.ts @@ -20,8 +20,9 @@ import { ConfigValidationError, ConfigWriteError, } from '../../errors'; +import { NoProjectError } from '../../errors'; import { detectAwsAccount } from '../../utils'; -import { NoProjectError, type PathConfig, PathResolver, findConfigRoot } from './path-resolver'; +import { type PathConfig, PathResolver, findConfigRoot } from './path-resolver'; import { loadSharedConfigFiles } from '@smithy/shared-ini-file-loader'; import { existsSync } from 'fs'; import { mkdir, readFile, writeFile } from 'fs/promises'; diff --git a/src/lib/schemas/io/index.ts b/src/lib/schemas/io/index.ts index 212468ffe..e5915541e 100644 --- a/src/lib/schemas/io/index.ts +++ b/src/lib/schemas/io/index.ts @@ -7,7 +7,6 @@ export { getSessionProjectRoot, getWorkingDirectory, requireConfigRoot, - NoProjectError, type PathConfig, } from './path-resolver'; export { ConfigIO, createConfigIO, getSchemaUrlForVersion } from './config-io'; diff --git a/src/lib/schemas/io/path-resolver.ts b/src/lib/schemas/io/path-resolver.ts index d79e54e89..e31bb2498 100644 --- a/src/lib/schemas/io/path-resolver.ts +++ b/src/lib/schemas/io/path-resolver.ts @@ -1,20 +1,14 @@ +<<<<<<< HEAD import { APP_DIR, CLI_LOGS_DIR, CLI_SYSTEM_DIR, CONFIG_DIR, CONFIG_FILES as _CONFIG_FILES } from '../../constants'; +======= +import { CLI_LOGS_DIR, CLI_SYSTEM_DIR, CONFIG_DIR, CONFIG_FILES as _CONFIG_FILES } from '../../constants'; +import { NoProjectError } from '../../errors'; +>>>>>>> origin/main import { existsSync } from 'fs'; import { dirname, join } from 'path'; -// Re-export for backward compatibility export const CONFIG_FILES = _CONFIG_FILES; -/** - * Error thrown when no AgentCore project is found. - */ -export class NoProjectError extends Error { - constructor(message?: string) { - super(message ?? 'No agentcore project found. Run "agentcore create" first.'); - this.name = 'NoProjectError'; - } -} - /** * Get the working directory where the user invoked the CLI. * diff --git a/src/schema/schemas/agentcore-project.ts b/src/schema/schemas/agentcore-project.ts index 9ca28dfda..704c9c177 100644 --- a/src/schema/schemas/agentcore-project.ts +++ b/src/schema/schemas/agentcore-project.ts @@ -11,8 +11,17 @@ import { AgentEnvSpecSchema } from './agent-env'; import { AgentCoreGatewaySchema, AgentCoreGatewayTargetSchema, AgentCoreMcpRuntimeToolSchema } from './mcp'; import { ABTestSchema } from './primitives/ab-test'; import { ConfigBundleSchema } from './primitives/config-bundle'; +<<<<<<< HEAD import { EvaluationLevelSchema, EvaluatorConfigSchema, EvaluatorNameSchema } from './primitives/evaluator'; import { HarnessNameSchema } from './primitives/harness'; +======= +import { + EvaluationLevelSchema, + EvaluatorConfigSchema, + EvaluatorNameSchema, + KmsKeyArnSchema, +} from './primitives/evaluator'; +>>>>>>> origin/main import { HttpGatewaySchema } from './primitives/http-gateway'; import { DEFAULT_EPISODIC_REFLECTION_NAMESPACES, @@ -46,7 +55,17 @@ export type { ManagedCodeBasedConfig, RatingScale, } from './primitives/evaluator'; +<<<<<<< HEAD export { BedrockModelIdSchema, isValidBedrockModelId, EvaluatorNameSchema } from './primitives/evaluator'; +======= +export { + BedrockModelIdSchema, + isValidBedrockModelId, + EvaluatorNameSchema, + KMS_KEY_ARN_PATTERN, + isValidKmsKeyArn, +} from './primitives/evaluator'; +>>>>>>> origin/main export { ConfigBundleSchema }; export type { ComponentConfiguration, ComponentConfigurationMap, ConfigBundle } from './primitives/config-bundle'; export { ConfigBundleNameSchema, ComponentConfigurationMapSchema } from './primitives/config-bundle'; @@ -59,6 +78,7 @@ export type { ABTestMode, TargetRef, GatewayFilter, PerVariantOnlineEvaluationCo export { ABTestModeSchema, TargetRefSchema, GatewayFilterSchema } from './primitives/ab-test'; export type { HttpGatewayTarget } from './primitives/http-gateway'; export { HttpGatewayTargetSchema } from './primitives/http-gateway'; +<<<<<<< HEAD export type { HarnessGatewayOutboundAuth, HarnessSpec, HarnessModelProvider } from './primitives/harness'; export { GatewayOAuthGrantTypeSchema, @@ -68,6 +88,8 @@ export { HarnessToolTypeSchema, HarnessModelProviderSchema, } from './primitives/harness'; +======= +>>>>>>> origin/main // ============================================================================ // ManagedBy Schema @@ -220,6 +242,7 @@ export const EvaluatorSchema = z.object({ level: EvaluationLevelSchema, description: z.string().optional(), config: EvaluatorConfigSchema, + kmsKeyArn: KmsKeyArnSchema.optional(), tags: TagsSchema.optional(), }); @@ -373,6 +396,7 @@ export const AgentCoreProjectSpecSchema = z name => `Duplicate HTTP gateway name: ${name}` ) ), +<<<<<<< HEAD harnesses: z .array(HarnessRegistryEntrySchema) @@ -383,6 +407,8 @@ export const AgentCoreProjectSpecSchema = z name => `Duplicate harness name: ${name}` ) ), +======= +>>>>>>> origin/main }) .strict() .superRefine((spec, ctx) => { diff --git a/src/schema/schemas/primitives/evaluator.ts b/src/schema/schemas/primitives/evaluator.ts index aa4f41cb4..97d772d52 100644 --- a/src/schema/schemas/primitives/evaluator.ts +++ b/src/schema/schemas/primitives/evaluator.ts @@ -119,3 +119,25 @@ export const EvaluatorConfigSchema = z }); export type EvaluatorConfig = z.infer; + +// ============================================================================ +// KMS Key ARN Validation +// ============================================================================ + +/** + * Pattern for KMS key ARNs accepted by the AgentCore Evaluation service. + * Matches key ARNs across all AWS partitions with a 36-char UUID key ID. + * Alias ARNs are not supported by the service for evaluator encryption. + */ +export const KMS_KEY_ARN_PATTERN = /^arn:[^:]+:kms:[a-zA-Z0-9-]*:[0-9]{12}:key\/[a-zA-Z0-9-]{36}$/; + +export const KmsKeyArnSchema = z + .string() + .regex( + KMS_KEY_ARN_PATTERN, + 'Must be a valid KMS key ARN (e.g. arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012)' + ); + +export function isValidKmsKeyArn(value: string): boolean { + return KMS_KEY_ARN_PATTERN.test(value); +} diff --git a/src/schema/schemas/primitives/index.ts b/src/schema/schemas/primitives/index.ts index 0a3c808cf..b92ffe887 100644 --- a/src/schema/schemas/primitives/index.ts +++ b/src/schema/schemas/primitives/index.ts @@ -66,6 +66,7 @@ export { ValidationModeSchema, } from './policy'; +<<<<<<< HEAD export type { HarnessGatewayOutboundAuth, HarnessMemoryRef, @@ -93,5 +94,7 @@ export { HarnessTruncationStrategySchema, } from './harness'; +======= +>>>>>>> origin/main export type { HttpGateway } from './http-gateway'; export { HttpGatewayNameSchema, HttpGatewaySchema } from './http-gateway';