From 14c9003a371e926916b3486ad5962f807e7e07b3 Mon Sep 17 00:00:00 2001 From: Avi Alpert Date: Fri, 1 May 2026 11:43:01 -0400 Subject: [PATCH] feat(agent-inspector): feat: add allowedTools and tools overrides to harness invocation API --- .../web-ui/__tests__/harness-utils.test.ts | 129 ++++++++++++++++++ src/cli/operations/dev/web-ui/api-types.ts | 12 +- .../dev/web-ui/handlers/harness-utils.ts | 10 +- 3 files changed, 137 insertions(+), 14 deletions(-) create mode 100644 src/cli/operations/dev/web-ui/__tests__/harness-utils.test.ts diff --git a/src/cli/operations/dev/web-ui/__tests__/harness-utils.test.ts b/src/cli/operations/dev/web-ui/__tests__/harness-utils.test.ts new file mode 100644 index 000000000..a7ac6c67e --- /dev/null +++ b/src/cli/operations/dev/web-ui/__tests__/harness-utils.test.ts @@ -0,0 +1,129 @@ +import type { HarnessInvocationOverrides } from '../api-types.js'; +import { buildInvokeOptions } from '../handlers/harness-utils.js'; +import { describe, expect, it } from 'vitest'; + +const BASE_ARN = 'arn:aws:bedrock-agentcore:us-west-2:123:harness/abc'; +const REGION = 'us-west-2'; +const SESSION_ID = 'sess-1'; +const MESSAGES = [{ role: 'user' as const, content: [{ text: 'hello' }] }]; + +describe('buildInvokeOptions', () => { + it('sets required fields with no overrides', () => { + const opts = buildInvokeOptions(BASE_ARN, REGION, SESSION_ID, MESSAGES); + + expect(opts).toMatchObject({ + harnessArn: BASE_ARN, + region: REGION, + runtimeSessionId: SESSION_ID, + messages: MESSAGES, + maxIterations: 75, + }); + }); + + it('forwards model override', () => { + const overrides: HarnessInvocationOverrides = { + model: { bedrockModelConfig: { modelId: 'anthropic.claude-v2' } }, + }; + + const opts = buildInvokeOptions(BASE_ARN, REGION, SESSION_ID, MESSAGES, overrides); + + expect(opts.model).toEqual({ bedrockModelConfig: { modelId: 'anthropic.claude-v2' } }); + }); + + it('wraps systemPrompt string into HarnessSystemPrompt array', () => { + const opts = buildInvokeOptions(BASE_ARN, REGION, SESSION_ID, MESSAGES, { + systemPrompt: 'Be concise', + }); + + expect(opts.systemPrompt).toEqual([{ text: 'Be concise' }]); + }); + + it('forwards skills', () => { + const overrides: HarnessInvocationOverrides = { + skills: [{ path: '/tools/search' }], + }; + + const opts = buildInvokeOptions(BASE_ARN, REGION, SESSION_ID, MESSAGES, overrides); + + expect(opts.skills).toEqual([{ path: '/tools/search' }]); + }); + + it('forwards actorId', () => { + const opts = buildInvokeOptions(BASE_ARN, REGION, SESSION_ID, MESSAGES, { + actorId: 'user-42', + }); + + expect(opts.actorId).toBe('user-42'); + }); + + it('forwards maxIterations', () => { + const opts = buildInvokeOptions(BASE_ARN, REGION, SESSION_ID, MESSAGES, { + maxIterations: 10, + }); + + expect(opts.maxIterations).toBe(10); + }); + + it('forwards maxTokens', () => { + const opts = buildInvokeOptions(BASE_ARN, REGION, SESSION_ID, MESSAGES, { + maxTokens: 1024, + }); + + expect(opts.maxTokens).toBe(1024); + }); + + it('forwards timeoutSeconds', () => { + const opts = buildInvokeOptions(BASE_ARN, REGION, SESSION_ID, MESSAGES, { + timeoutSeconds: 30, + }); + + expect(opts.timeoutSeconds).toBe(30); + }); + + it('forwards allowedTools', () => { + const opts = buildInvokeOptions(BASE_ARN, REGION, SESSION_ID, MESSAGES, { + allowedTools: ['tool-a', 'tool-b'], + }); + + expect(opts.allowedTools).toEqual(['tool-a', 'tool-b']); + }); + + it('forwards tools', () => { + const overrides: HarnessInvocationOverrides = { + tools: [ + { type: 'remote_mcp', name: 'my-mcp', config: { url: 'https://example.com' } }, + { type: 'inline_function', name: 'calc', config: { fn: 'add' } }, + ], + }; + + const opts = buildInvokeOptions(BASE_ARN, REGION, SESSION_ID, MESSAGES, overrides); + + expect(opts.tools).toEqual(overrides.tools); + }); + + it('forwards all overrides together', () => { + const overrides: HarnessInvocationOverrides = { + model: { openAiModelConfig: { modelId: 'gpt-4' } }, + systemPrompt: 'You are helpful', + skills: [{ path: '/s' }], + actorId: 'actor-1', + maxIterations: 5, + maxTokens: 256, + timeoutSeconds: 60, + allowedTools: ['tool-x'], + tools: [{ type: 'remote_mcp', name: 'mcp-1', config: {} }], + }; + + const opts = buildInvokeOptions(BASE_ARN, REGION, SESSION_ID, MESSAGES, overrides); + + expect(opts.model).toEqual(overrides.model); + expect(opts.systemPrompt).toEqual([{ text: 'You are helpful' }]); + expect(opts.skills).toEqual(overrides.skills); + expect(opts.actorId).toBe('actor-1'); + expect(opts.maxIterations).toBe(5); + expect(opts.maxTokens).toBe(256); + expect(opts.timeoutSeconds).toBe(60); + expect(opts.allowedTools).toEqual(['tool-x']); + expect(opts.tools).toEqual(overrides.tools); + }); +}); diff --git a/src/cli/operations/dev/web-ui/api-types.ts b/src/cli/operations/dev/web-ui/api-types.ts index ff9f832d1..61e0c80a2 100644 --- a/src/cli/operations/dev/web-ui/api-types.ts +++ b/src/cli/operations/dev/web-ui/api-types.ts @@ -8,6 +8,7 @@ * TODO: Extract these types into a shared package so both repos import * from a single source of truth instead of manually duplicating. */ +import type { HarnessModelConfiguration, HarnessTool } from '../../../aws/agentcore-harness'; import type { CloudWatchSpanRecord, CloudWatchTraceRecord } from '../../traces/types'; // --------------------------------------------------------------------------- @@ -285,20 +286,15 @@ export interface InvocationRequest { /** Overrides sent with harness invocations */ export interface HarnessInvocationOverrides { - model?: HarnessModelOverride; + model?: HarnessModelConfiguration; systemPrompt?: string; skills?: { path: string }[]; actorId?: string; maxIterations?: number; maxTokens?: number; timeoutSeconds?: number; -} - -/** Model override — exactly one provider field should be set */ -export interface HarnessModelOverride { - bedrockModelConfig?: { modelId: string }; - openAiModelConfig?: { modelId: string }; - geminiModelConfig?: { modelId: string }; + allowedTools?: string[]; + tools?: HarnessTool[]; } // --------------------------------------------------------------------------- diff --git a/src/cli/operations/dev/web-ui/handlers/harness-utils.ts b/src/cli/operations/dev/web-ui/handlers/harness-utils.ts index a515eb724..4a2947e9e 100644 --- a/src/cli/operations/dev/web-ui/handlers/harness-utils.ts +++ b/src/cli/operations/dev/web-ui/handlers/harness-utils.ts @@ -1,8 +1,4 @@ -import type { - HarnessModelConfiguration, - HarnessSystemPrompt, - InvokeHarnessOptions, -} from '../../../../aws/agentcore-harness'; +import type { HarnessSystemPrompt, InvokeHarnessOptions } from '../../../../aws/agentcore-harness'; import type { HarnessInvocationOverrides } from '../api-types'; const DEFAULT_MAX_ITERATIONS = 75; @@ -21,13 +17,15 @@ export function buildInvokeOptions( messages, }; - if (overrides?.model) opts.model = overrides.model as HarnessModelConfiguration; + if (overrides?.model) opts.model = overrides.model; if (overrides?.systemPrompt) opts.systemPrompt = [{ text: overrides.systemPrompt }] as HarnessSystemPrompt; if (overrides?.skills) opts.skills = overrides.skills; if (overrides?.actorId) opts.actorId = overrides.actorId; opts.maxIterations = overrides?.maxIterations ?? DEFAULT_MAX_ITERATIONS; if (overrides?.maxTokens != null) opts.maxTokens = overrides.maxTokens; if (overrides?.timeoutSeconds != null) opts.timeoutSeconds = overrides.timeoutSeconds; + if (overrides?.allowedTools) opts.allowedTools = overrides.allowedTools; + if (overrides?.tools) opts.tools = overrides.tools; return opts; }