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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7430,7 +7430,7 @@ file maps to a JSON config file and includes validation constraints as comments
- **NetworkMode**: \`'PUBLIC'\` | \`'VPC'\`
- **RuntimeVersion**: \`'PYTHON_3_10'\` | \`'PYTHON_3_11'\` | \`'PYTHON_3_12'\` | \`'PYTHON_3_13'\` | \`'PYTHON_3_14'\` | \`'NODE_18'\` | \`'NODE_20'\` | \`'NODE_22'\`
- **MemoryStrategyType**: \`'SEMANTIC'\` | \`'SUMMARIZATION'\` | \`'USER_PREFERENCE'\` | \`'EPISODIC'\`
- **GatewayTargetType**: \`'lambda'\` | \`'mcpServer'\` | \`'openApiSchema'\` | \`'smithyModel'\` | \`'apiGateway'\` | \`'lambdaFunctionArn'\` | \`'connector'\` (web-search, bedrock-knowledge-bases, bedrock-agentic-retrieve)
- **GatewayTargetType**: \`'lambda'\` | \`'mcpServer'\` | \`'openApiSchema'\` | \`'smithyModel'\` | \`'apiGateway'\` | \`'lambdaFunctionArn'\` | \`'connector'\` (web-search, bedrock-knowledge-bases)
- **ModelProvider**: \`'Bedrock'\` | \`'Gemini'\` | \`'OpenAI'\` | \`'Anthropic'\`
- **PaymentProvider**: \`'CoinbaseCDP'\` | \`'StripePrivy'\`
- **PolicyEnforcementMode**: \`'ACTIVE'\` | \`'PASSIVE'\`
Expand Down
2 changes: 1 addition & 1 deletion src/assets/agents/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ file maps to a JSON config file and includes validation constraints as comments
- **NetworkMode**: `'PUBLIC'` | `'VPC'`
- **RuntimeVersion**: `'PYTHON_3_10'` | `'PYTHON_3_11'` | `'PYTHON_3_12'` | `'PYTHON_3_13'` | `'PYTHON_3_14'` | `'NODE_18'` | `'NODE_20'` | `'NODE_22'`
- **MemoryStrategyType**: `'SEMANTIC'` | `'SUMMARIZATION'` | `'USER_PREFERENCE'` | `'EPISODIC'`
- **GatewayTargetType**: `'lambda'` | `'mcpServer'` | `'openApiSchema'` | `'smithyModel'` | `'apiGateway'` | `'lambdaFunctionArn'` | `'connector'` (web-search, bedrock-knowledge-bases, bedrock-agentic-retrieve)
- **GatewayTargetType**: `'lambda'` | `'mcpServer'` | `'openApiSchema'` | `'smithyModel'` | `'apiGateway'` | `'lambdaFunctionArn'` | `'connector'` (web-search, bedrock-knowledge-bases)
- **ModelProvider**: `'Bedrock'` | `'Gemini'` | `'OpenAI'` | `'Anthropic'`
- **PaymentProvider**: `'CoinbaseCDP'` | `'StripePrivy'`
- **PolicyEnforcementMode**: `'ACTIVE'` | `'PASSIVE'`
Expand Down
7 changes: 3 additions & 4 deletions src/cli/commands/add/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,12 @@ export interface AddGatewayTargetOptions {
schemaS3Account?: string;
runtime?: string;
runtimeEndpoint?: string;
/** Connector id (for --type connector): bedrock-knowledge-bases | bedrock-agentic-retrieve | web-search. */
/** Connector id (for --type connector): bedrock-knowledge-bases | web-search. */
connector?: string;
/**
* KB reference for --type connector — either a project KB name (entry in
* knowledgeBases[]) or a literal 10-char external KB ID. Repeatable when
* --connector is bedrock-agentic-retrieve (fan-out); single-valued for
* bedrock-knowledge-bases. Not applicable to --connector web-search.
* knowledgeBases[]) or a literal 10-char external KB ID. Not applicable to
* --connector web-search.
*/
knowledgeBaseId?: string[];
passthroughEndpoint?: string;
Expand Down
64 changes: 17 additions & 47 deletions src/cli/commands/add/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,9 @@
options.language =
(matchEnumValue(TargetLanguageSchema, options.language) as typeof options.language) ?? options.language;

if (!options.name) {
const kbConnectors = ['bedrock-knowledge-bases'];
const nameOptional = options.type === 'connector' && kbConnectors.includes(options.connector ?? '');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nameOptional only covers the two KB connectors, so --connector web-search without --name still hits the --name is required branch even though the primitive at GatewayTargetPrimitive.ts:665-669 has default-name logic for the KB connectors only. If the PR intent is "defaults for KB, name required for web-search", that's consistent — but the PR description says "A default value is given for both KB creation, and attaching connector if none is provided", which reads as "for all connectors". Please either extend the default to web-search too (e.g. web-search or web-search-${gateway}) or clarify the description.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional, I've updated PR description to clarify

if (!options.name && !nameOptional) {
return { valid: false, error: '--name is required' };
}

Expand All @@ -407,7 +409,6 @@
'http-runtime',
'connector',
'passthrough',
'web-search',
].join(', ');

if (!options.type) {
Expand All @@ -426,7 +427,6 @@
'http-runtime': 'httpRuntime',
connector: 'connector',
passthrough: 'passthrough',
'web-search': 'webSearch',
};
const mappedType = typeMap[options.type];
if (!mappedType) {
Expand All @@ -437,11 +437,11 @@
}
options.type = mappedType;

// --exclude-domains is webSearch-target-only. Reject it on every other target type.
if (mappedType !== 'webSearch' && options.excludeDomains) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We should consider a cleaner way to do flag validation in our rf.

// --exclude-domains only applies to the web-search connector.
if (options.excludeDomains && !(mappedType === 'connector' && options.connector === 'web-search')) {
return {
valid: false,
error: '--exclude-domains only applies to --type web-search',
error: '--exclude-domains only applies to --connector web-search',
};
}

Expand Down Expand Up @@ -630,43 +630,6 @@
return { valid: true };
}

// Web search targets (Amazon Web Search managed connector): validate early and return
if (mappedType === 'webSearch') {
const WEB_SEARCH_DISALLOWED_OPTIONS: [string, string][] = [
['connector', '--connector'],
['knowledgeBaseId', '--knowledge-base-id'],
['endpoint', '--endpoint'],
['host', '--host'],
['restApiId', '--rest-api-id'],
['stage', '--stage'],
['lambdaArn', '--lambda-arn'],
['toolSchemaFile', '--tool-schema-file'],
['toolFilterPath', '--tool-filter-path'],
['toolFilterMethods', '--tool-filter-methods'],
['schema', '--schema'],
['schemaS3Account', '--schema-s3-account'],
['outboundAuthType', '--outbound-auth'],
['credentialName', '--credential-name'],
['oauthClientId', '--oauth-client-id'],
['oauthClientSecret', '--oauth-client-secret'],
['oauthDiscoveryUrl', '--oauth-discovery-url'],
['oauthScopes', '--oauth-scopes'],
];
for (const [key, flag] of WEB_SEARCH_DISALLOWED_OPTIONS) {
const v = (options as unknown as Record<string, unknown>)[key];
// knowledgeBaseId is a string[]; treat empty array as absent
const present = Array.isArray(v) ? v.length > 0 : !!v;
if (present) {
return { valid: false, error: `${flag} is not applicable for web-search type` };
}
}
if (options.language && options.language !== 'Other') {
return { valid: false, error: '--language is not applicable for web-search type' };
}
options.language = 'Other';
return { valid: true };
}

// Connector targets (Bedrock KB, agentic-retrieve): validate early and return
if (mappedType === 'connector') {
const validConnectors = CONNECTOR_ID_VALUES.join(', ');
Expand All @@ -682,17 +645,22 @@
error: `Invalid --connector "${options.connector}". Valid: ${validConnectors}`,
};
}
if (!options.knowledgeBaseId || options.knowledgeBaseId.length === 0) {
if (options.connector === 'web-search' && options.knowledgeBaseId && options.knowledgeBaseId.length > 0) {
return {
valid: false,
error: '--knowledge-base-id is not applicable for --connector web-search',
};
}
if (options.connector !== 'web-search' && (!options.knowledgeBaseId || options.knowledgeBaseId.length === 0)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Correctness — lost early error] The connector irrelevant list (just below) dropped ['schema','--schema'] and ['schemaS3Account','--schema-s3-account'], both of which were in the deleted WEB_SEARCH_DISALLOWED_OPTIONS. So --connector web-search --schema s3://... no longer gets the clean early CLI error '--schema is not applicable', and a bare --schema-s3-account (no --schema) slips past the CLI entirely. Lower severity since a materialized schemaSource may still trip the schema layer, but the early feedback is gone.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added --schema/--schema-s3-account to the connector irrelevant list

return {
valid: false,
error: `--knowledge-base-id is required for --connector ${options.connector}`,
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This skips the --knowledge-base-id is required check for web-search (correct), but nothing rejects --knowledge-base-id being supplied when --connector web-search. Suggest adding an explicit rejection so --connector web-search --knowledge-base-id foo fails with a clear message rather than silently ignoring the flag.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added explicit check & rejection

if (options.connector === 'bedrock-knowledge-bases' && options.knowledgeBaseId.length > 1) {
if (options.connector === 'bedrock-knowledge-bases' && (options.knowledgeBaseId?.length ?? 0) > 1) {
return {
valid: false,
error:
'--knowledge-base-id may only be specified once for --connector bedrock-knowledge-bases. Use --connector bedrock-agentic-retrieve for fan-out.',
error: '--knowledge-base-id may only be specified once for --connector bedrock-knowledge-bases.',
};
}
const irrelevant: [string, string][] = [
Expand All @@ -704,6 +672,8 @@
['toolSchemaFile', '--tool-schema-file'],
['toolFilterPath', '--tool-filter-path'],
['toolFilterMethods', '--tool-filter-methods'],
['schema', '--schema'],
['schemaS3Account', '--schema-s3-account'],
['outboundAuthType', '--outbound-auth'],
['credentialName', '--credential-name'],
['oauthClientId', '--oauth-client-id'],
Expand All @@ -729,7 +699,7 @@
if (!passthroughEndpoint) {
return { valid: false, error: '--passthrough-endpoint is required for passthrough type' };
}
if (!/^https:\/\/[a-zA-Z0-9\-.]+(:[0-9]{1,5})?(\/.*)?$/.test(passthroughEndpoint)) {

Check warning on line 702 in src/cli/commands/add/validate.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe Regular Expression
return { valid: false, error: '--passthrough-endpoint must be a valid HTTPS URL' };
}
if (options.language && options.language !== 'Other') {
Expand Down
61 changes: 47 additions & 14 deletions src/cli/commands/status/__tests__/action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ describe('computeResourceStatuses', () => {
name: 'docs',
targetType: 'connector',
connectorId: 'bedrock-knowledge-bases',
knowledgeBaseId: 'docs',
configurations: [{ name: 'Retrieve', parameterValues: { knowledgeBaseId: 'docs' } }],
},
],
},
Expand All @@ -653,15 +653,26 @@ describe('computeResourceStatuses', () => {
name: 'main-gw',
targets: [
{ name: 't1', targetType: 'mcpServer' },
{ name: 'docs', targetType: 'connector', connectorId: 'bedrock-knowledge-bases', knowledgeBaseId: 'docs' },
{
name: 'docs',
targetType: 'connector',
connectorId: 'bedrock-knowledge-bases',
configurations: [
{ name: 'Retrieve', parameterValues: { knowledgeBaseId: 'docs' } },
{
name: 'AgenticRetrieveStream',
parameterValues: { retrievers: [{ configuration: { knowledgeBase: { knowledgeBaseId: 'docs' } } }] },
},
],
},
],
},
],
} as unknown as AgentCoreProjectSpec;

const result = computeResourceStatuses(project, undefined);
const gwEntry = result.find(r => r.resourceType === 'gateway' && r.name === 'main-gw');
expect(gwEntry?.detail).toBe('2 targets (1 retrieve)');
expect(gwEntry?.detail).toBe('2 targets (1 retrieve, agentic ×1)');
});

it('annotates gateway detail with both retrieve count and agentic fan-out', () => {
Expand All @@ -671,13 +682,29 @@ describe('computeResourceStatuses', () => {
{
name: 'main-gw',
targets: [
{ name: 'docs', targetType: 'connector', connectorId: 'bedrock-knowledge-bases', knowledgeBaseId: 'docs' },
{ name: 'hr', targetType: 'connector', connectorId: 'bedrock-knowledge-bases', knowledgeBaseId: 'hr' },
{
name: 'main-gw-agentic',
name: 'docs',
targetType: 'connector',
connectorId: 'bedrock-knowledge-bases',
configurations: [
{ name: 'Retrieve', parameterValues: { knowledgeBaseId: 'docs' } },
{
name: 'AgenticRetrieveStream',
parameterValues: { retrievers: [{ configuration: { knowledgeBase: { knowledgeBaseId: 'docs' } } }] },
},
],
},
{
name: 'hr',
targetType: 'connector',
connectorId: 'bedrock-agentic-retrieve',
knowledgeBaseIds: ['docs', 'hr'],
connectorId: 'bedrock-knowledge-bases',
configurations: [
{ name: 'Retrieve', parameterValues: { knowledgeBaseId: 'hr' } },
{
name: 'AgenticRetrieveStream',
parameterValues: { retrievers: [{ configuration: { knowledgeBase: { knowledgeBaseId: 'hr' } } }] },
},
],
},
],
},
Expand All @@ -686,21 +713,27 @@ describe('computeResourceStatuses', () => {

const result = computeResourceStatuses(project, undefined);
const gwEntry = result.find(r => r.resourceType === 'gateway' && r.name === 'main-gw');
expect(gwEntry?.detail).toBe('3 targets (2 retrieve, agentic ×2)');
expect(gwEntry?.detail).toBe('2 targets (2 retrieve, agentic ×2)');
});

it('KB detail surfaces wiring from agentic-retrieve fan-out target', () => {
it('KB detail surfaces wiring from configurations', () => {
const project = {
...baseProject,
agentCoreGateways: [
{
name: 'main-gw',
targets: [
{
name: 'main-gw-agentic',
name: 'docs-target',
targetType: 'connector',
connectorId: 'bedrock-agentic-retrieve',
knowledgeBaseIds: ['docs'],
connectorId: 'bedrock-knowledge-bases',
configurations: [
{ name: 'Retrieve', parameterValues: { knowledgeBaseId: 'docs' } },
{
name: 'AgenticRetrieveStream',
parameterValues: { retrievers: [{ configuration: { knowledgeBase: { knowledgeBaseId: 'docs' } } }] },
},
],
},
],
},
Expand Down Expand Up @@ -929,7 +962,7 @@ describe('handleProjectStatus — knowledge base enrichment', () => {
name: 'docs',
targetType: 'connector',
connectorId: 'bedrock-knowledge-bases',
knowledgeBaseId: 'product-docs',
configurations: [{ name: 'Retrieve', parameterValues: { knowledgeBaseId: 'product-docs' } }],
},
],
},
Expand Down
64 changes: 48 additions & 16 deletions src/cli/commands/status/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,21 @@ export function computeResourceStatuses(
const retrieveCount = targets.filter(
t => t.targetType === 'connector' && t.connectorId === 'bedrock-knowledge-bases'
).length;
const agentic = targets.find(t => t.targetType === 'connector' && t.connectorId === 'bedrock-agentic-retrieve');
const webSearchCount = targets.filter(t => t.targetType === 'webSearch').length;
const webSearchCount = targets.filter(t => t.targetType === 'connector' && t.connectorId === 'web-search').length;
const base = `${targets.length} target${targets.length !== 1 ? 's' : ''}`;
const parts: string[] = [];
if (retrieveCount > 0) parts.push(`${retrieveCount} retrieve`);
if (agentic) {
const fanOut = agentic.knowledgeBaseIds?.length ?? 0;
parts.push(`agentic ×${fanOut}`);
// Count agentic retrievers from AgenticRetrieveStream configurations within KB connector targets
let agenticFanOut = 0;
for (const t of targets) {
if (t.targetType !== 'connector' || t.connectorId !== 'bedrock-knowledge-bases') continue;
const agenticConfig = (t.configurations ?? []).find(c => c.name === 'AgenticRetrieveStream');
if (agenticConfig) {
const retrievers = agenticConfig.parameterValues?.retrievers as unknown[] | undefined;
agenticFanOut += retrievers?.length ?? 0;
}
}
if (agenticFanOut > 0) parts.push(`agentic ×${agenticFanOut}`);
if (webSearchCount > 0) parts.push(`${webSearchCount} web-search`);
return parts.length > 0 ? `${base} (${parts.join(', ')})` : base;
},
Expand Down Expand Up @@ -248,9 +254,8 @@ export function computeResourceStatuses(
});

// Reverse-index: KB name -> list of gateways with a connector target referencing it.
// Walks both knowledgeBaseId (single-KB Retrieve) and knowledgeBaseIds[]
// (agentic-retrieve fan-out) so a KB shows its wiring no matter which
// connector kind references it.
// Walks both Retrieve (knowledgeBaseId) and AgenticRetrieveStream (retrievers[])
// configurations so a KB shows its wiring no matter which configuration references it.
const kbToGateways = new Map<string, Set<string>>();
const recordKbWiring = (kbRef: string, gatewayName: string): void => {
const set = kbToGateways.get(kbRef) ?? new Set<string>();
Expand All @@ -260,8 +265,22 @@ export function computeResourceStatuses(
for (const gw of project.agentCoreGateways ?? []) {
for (const t of gw.targets ?? []) {
if (t.targetType !== 'connector') continue;
if (t.knowledgeBaseId) recordKbWiring(t.knowledgeBaseId, gw.name);
for (const ref of t.knowledgeBaseIds ?? []) recordKbWiring(ref, gw.name);
for (const cfg of t.configurations ?? []) {
const pv = cfg.parameterValues;
if (cfg.name === 'Retrieve') {
const kbId = pv?.knowledgeBaseId as string | undefined;
if (kbId) recordKbWiring(kbId, gw.name);
}
if (cfg.name === 'AgenticRetrieveStream') {
const retrievers = pv?.retrievers as
| { configuration?: { knowledgeBase?: { knowledgeBaseId?: string } } }[]
| undefined;
for (const r of retrievers ?? []) {
const ref = r?.configuration?.knowledgeBase?.knowledgeBaseId;
if (ref) recordKbWiring(ref, gw.name);
}
}
}
}
}

Expand Down Expand Up @@ -532,10 +551,9 @@ export async function handleProjectStatus(
logger.startStep(`Fetch knowledge base status (${deployedKbs.length} KB${deployedKbs.length !== 1 ? 's' : ''})`);

// Reverse-index: KB spec name -> gateways whose connector targets
// reference it. Project-owned KBs are stored by *name* on connector
// targets (single-KB Retrieve on `knowledgeBaseId`, agentic-retrieve
// fan-out on `knowledgeBaseIds[]`), so we key by the spec name
// (entry.name) below.
// reference it. Project-owned KBs are stored by *name* in connector
// target configurations (Retrieve on `knowledgeBaseId`, AgenticRetrieveStream
// on `retrievers[]`), so we key by the spec name (entry.name) below.
const kbNameToGateways = new Map<string, Set<string>>();
const recordKbWiring = (kbRef: string, gatewayName: string): void => {
const set = kbNameToGateways.get(kbRef) ?? new Set<string>();
Expand All @@ -545,8 +563,22 @@ export async function handleProjectStatus(
for (const gw of project.agentCoreGateways ?? []) {
for (const t of gw.targets ?? []) {
if (t.targetType !== 'connector') continue;
if (t.knowledgeBaseId) recordKbWiring(t.knowledgeBaseId, gw.name);
for (const ref of t.knowledgeBaseIds ?? []) recordKbWiring(ref, gw.name);
for (const cfg of t.configurations ?? []) {
const pv = cfg.parameterValues;
if (cfg.name === 'Retrieve') {
const kbId = pv?.knowledgeBaseId as string | undefined;
if (kbId) recordKbWiring(kbId, gw.name);
}
if (cfg.name === 'AgenticRetrieveStream') {
const retrievers = pv?.retrievers as
| { configuration?: { knowledgeBase?: { knowledgeBaseId?: string } } }[]
| undefined;
for (const r of retrievers ?? []) {
const ref = r?.configuration?.knowledgeBase?.knowledgeBaseId;
if (ref) recordKbWiring(ref, gw.name);
}
}
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/cli/operations/connectors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export { translateConnector } from './translators';
export type {
ConfigurationEntry,
ConnectorTranslatorInput,
ParameterOverride,
WebSearchTranslatorInput,
KnowledgeBasesTranslatorInput,
} from './translators';
Loading
Loading