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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@alicloud/tea-util": "^1.4.8",
"@serverless-cd/srm-aliyun-sls20201230": "0.0.4",
"@serverless-devs/component-interface": "^0.0.4",
"@serverless-devs/downloads": "^0.0.7",
"@serverless-devs/load-component": "*",
"@serverless-devs/logger": "^0.0.5",
"@serverless-devs/utils": "^0.0.15",
Expand All @@ -48,6 +49,7 @@
"ali-oss": "^6.20.0",
"axios": "^1.7.9",
"chalk": "^4.1.2",
"fs-extra": "^11.3.3",
"inquirer": "^8.2.6",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
Expand Down
92 changes: 34 additions & 58 deletions src/impl/agentrun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
removeCustomDomain,
infoCustomDomain,
} from "./custom_domain";
import * as $OpenApi from "@alicloud/openapi-client";
import { parseArgv, getRootHome } from "@serverless-devs/utils";
import * as _ from "lodash";
import GLogger from "../common/logger";
Expand All @@ -33,7 +32,6 @@ import Client, {
NASMountConfig,
RoutingConfiguration,
VersionWeight,
ListAgentRuntimesRequest,
GetAgentRuntimeRequest,
ListAgentRuntimeEndpointsRequest,
ListWorkspacesRequest,
Expand All @@ -44,11 +42,15 @@ import Client, {
DeleteAgentRuntimeEndpointRequest,
DeleteAgentRuntimeRequest,
} from "@alicloud/agentrun20250910";
import { agentRunRegionEndpoints } from "../common/constant";
import { verify, verifyDelete } from "../utils/verify";
import { AgentRuntimeOutput } from "./output";
import { promptForConfirmOrDetails } from "../utils/inquire";
import { sleep } from "@alicloud/tea-typescript";
import { initAgentRunClient } from "../utils/client";
import {
resolveWorkspaceId,
getAgentRuntimeIdByWorkspace,
} from "../utils/agentRuntimeQuery";

// 新增导入
import FC2 from "@alicloud/fc2";
Expand Down Expand Up @@ -392,32 +394,11 @@ export class AgentRun {
}

private async initClient(command: string) {
const {
AccessKeyID: accessKeyId,
AccessKeySecret: accessKeySecret,
SecurityToken: securityToken,
} = await this.inputs.getCredential();

const endpoint = agentRunRegionEndpoints.get(this.region);
if (!endpoint) {
throw new Error(`no agentrun endpoint found for ${this.region}`);
}
const protocol = "https";
const clientConfig = new $OpenApi.Config({
accessKeyId,
accessKeySecret,
securityToken,
protocol,
endpoint: endpoint,
readTimeout: 60000,
connectTimeout: 5000,
userAgent: `${
this.inputs.userAgent ||
`Component:agentrun;Nodejs:${process.version};OS:${process.platform}-${process.arch}`
}command:${command}`,
});

this.agentRuntimeClient = new Client(clientConfig);
this.agentRuntimeClient = await initAgentRunClient(
this.inputs,
this.region,
command,
);
}

/**
Expand Down Expand Up @@ -698,38 +679,33 @@ logConfig:

private async findAgentRuntimeByName(): Promise<string> {
const logger = GLogger.getLogger();
const listRequest = new ListAgentRuntimesRequest();
listRequest.agentRuntimeName = this.agentRuntimeConfig.agentRuntimeName;
listRequest.pageNumber = 1;
listRequest.pageSize = 100;
listRequest.searchMode = "exact";
const workspaceConfig = this.workspace;
let workspaceId: string | undefined = undefined;
let workspaceName: string | undefined = undefined;

if (workspaceConfig) {
workspaceId = workspaceConfig.id;
workspaceName = workspaceConfig.name;
}

try {
const result =
await this.agentRuntimeClient.listAgentRuntimes(listRequest);
if (result.statusCode != 200) {
logger.error(
`list agent runtimes failed, statusCode: ${result.statusCode}, requestId: ${result.body?.requestId}`,
);
return "";
}
if (_.isEmpty(result.body?.data?.items)) {
logger.debug(
`no agent runtime found with name ${this.agentRuntimeConfig.agentRuntimeName}`,
);
return "";
}
const runtime = result.body.data.items.find(
(item) =>
item.agentRuntimeName == this.agentRuntimeConfig.agentRuntimeName,
const resolvedWorkspaceId = await resolveWorkspaceId(
this.agentRuntimeClient,
this.inputs,
workspaceId,
workspaceName,
);
if (runtime == undefined) {
return "";
}
return runtime.agentRuntimeId || "";
} catch (e) {
logger.error(`list agent runtimes failed, message: ${e.message}`);
throw e;

const runtimeId = await getAgentRuntimeIdByWorkspace(
this.agentRuntimeClient,
this.agentRuntimeConfig.agentRuntimeName,
resolvedWorkspaceId,
);

return runtimeId;
} catch (error: any) {
logger.error(`Failed to find agent runtime: ${error.message}`);
throw error;
}
}

Expand Down
62 changes: 62 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Instance from "./subCommands/instance";
import Concurrency from "./subCommands/concurrency";
import Version from "./subCommands/version";
import Endpoint from "./subCommands/endpoint";
import Sync from "./subCommands/sync";

const FC3_COMPONENT_NAME = "fc3";

Expand Down Expand Up @@ -499,6 +500,60 @@ export default class ComponentAgentRun {
},
},
},
sync: {
help: {
description: `Sync agent runtime configuration and code from cloud to local.

This command downloads the complete agent runtime configuration and code
from the cloud and saves them as local YAML and code files.

Examples with CLI:
# Sync agent configuration and code to default directory
$ s cli agentrun sync --region cn-hangzhou --agent-name my-agent

# Sync to custom target directory
$ s cli agentrun sync --region cn-hangzhou --agent-name my-agent --target-dir ./my-local-agent

# Sync specific version or alias
$ s cli agentrun sync --region cn-hangzhou --agent-name my-agent --qualifier v1

# Sync with debug mode
$ s cli agentrun sync --region cn-hangzhou --agent-name my-agent --debug

# Skip EventBridge triggers
$ s cli agentrun sync --region cn-hangzhou --agent-name my-agent --disable-list-remote-eb-triggers true
`,
summary: "Sync agent runtime from cloud to local",
option: [
[
"--region <region>",
"Region where the agent runtime is deployed (required)",
],
[
"--agent-name <name>",
"Name of the agent runtime to sync (required)",
],
[
"--target-dir <dir>",
"Target directory for synced files (optional, default: ./sync-clone)",
],
[
"--qualifier <qualifier>",
"Version or alias qualifier (optional, default: LATEST)",
],
["--workspace-id <id>", "Workspace ID (optional)"],
["--workspace-name <name>", "Workspace name (optional)"],
[
"--disable-list-remote-eb-triggers <value>",
"Disable listing EventBridge triggers (optional)",
],
[
"--disable-list-remote-alb-triggers <value>",
"Disable listing ALB triggers (optional)",
],
],
},
},
};
}

Expand Down Expand Up @@ -691,4 +746,11 @@ export default class ComponentAgentRun {
const endpoint = new Endpoint(inputs);
return await endpoint[endpoint.subCommand]();
}

public async sync(inputs: IInputs): Promise<any> {
GLogger.setLogger(this.logger);
GLogger.getLogger().debug(`sync ==> input: ${JSON.stringify(inputs)}`);
const sync = new Sync(inputs);
return await sync.run();
}
}
5 changes: 5 additions & 0 deletions src/interface/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ArmsConfiguration } from "@alicloud/agentrun20250910";
import { OSSMountConfig } from "@alicloud/fc20230330";
import { IInputs as _IInputs } from "@serverless-devs/component-interface";

export interface IInputs extends _IInputs {
Expand Down Expand Up @@ -58,6 +59,9 @@ export interface AgentConfig {
// NAS 文件存储配置
nasConfig?: NasConfig;

// OSS 挂载配置
ossMountConfig?: OSSMountConfig;

// 环境变量
environmentVariables?: { [key: string]: string };

Expand Down Expand Up @@ -264,6 +268,7 @@ export interface AgentRuntimeConfig {
sessionIdleTimeoutSeconds?: number;
networkConfiguration?: NetworkConfiguration;
nasConfig?: NasConfigInternal;
ossMountConfig?: OSSMountConfig;
environmentVariables?: { [key: string]: string };
executionRoleArn?: string;
credentialName?: string;
Expand Down
2 changes: 2 additions & 0 deletions src/subCommands/sync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import sync from "./sync/index";
export default sync;
68 changes: 68 additions & 0 deletions src/subCommands/sync/agentRuntimeAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import _ from "lodash";
import Client, { GetAgentRuntimeRequest } from "@alicloud/agentrun20250910";
import { IInputs } from "../../interface";
import { AgentRuntimeInfo } from "./types";
import { initAgentRunClient } from "../../utils/client";

export class AgentRuntimeAPI {
private client?: Client;
private inputs: IInputs;
private region: string;

constructor(inputs: IInputs, region: string) {
this.inputs = inputs;
this.region = region;
}

/**
* 获取客户端实例
*/
private async getClient(): Promise<Client> {
if (!this.client) {
this.client = await initAgentRunClient(this.inputs, this.region, "sync");
}
return this.client;
}

/**
* 获取完整的 Agent Runtime 信息 (兼容原有接口)
* 主要用于补充缺失的字段(如endpoints)
*/
public async getCompleteAgentRuntimeInfo(
runtimeId: string,
): Promise<AgentRuntimeInfo> {
const client = await this.getClient();
const getRequest = new GetAgentRuntimeRequest();

try {
const result = await client.getAgentRuntime(runtimeId, getRequest);
if (result.statusCode !== 200) {
throw new Error(
`Failed to get agent runtime info, statusCode: ${result.statusCode}, requestId: ${result.body?.requestId}`,
);
}

if (!result.body?.data) {
throw new Error(`No agent runtime data found for ${runtimeId}`);
}

const agentRuntimeData: any = result.body.data;

// 确保必需字段存在
if (!agentRuntimeData.agentRuntimeId) {
throw new Error(
`Agent runtime data missing required field 'agentRuntimeId'`,
);
}
if (!agentRuntimeData.agentRuntimeName) {
throw new Error(
`Agent runtime data missing required field 'agentRuntimeName'`,
);
}

return agentRuntimeData;
} catch (error: any) {
throw error;
}
}
}
62 changes: 62 additions & 0 deletions src/subCommands/sync/codeDownloader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import fs_extra from "fs-extra";
import downloads from "@serverless-devs/downloads";

/**
* 从 OSS 下载代码(通过 bucket 和 object name)
*/
export async function downloadCodeFromOSS(
ossBucketName: string,
ossObjectName: string,
region: string,
codePath: string,
baseDir: string,
): Promise<string> {
const ossEndpoint = `https://${ossBucketName}.oss-${region}.aliyuncs.com`;
const downloadUrl = `${ossEndpoint}/${ossObjectName}`;

await fs_extra.removeSync(codePath);

let codeUrl = downloadUrl;
if (process.env.FC_REGION === region) {
codeUrl = downloadUrl.replace(".aliyuncs.com", "-internal.aliyuncs.com");
}

await downloads(codeUrl, {
dest: codePath,
extract: true,
});

return codePath;
}

/**
* 从 URL 下载代码(通过 FC API 获取的临时 URL)
*/
export async function downloadCodeFromURL(
codeUrl: string,
region: string,
codePath: string,
): Promise<string> {
await fs_extra.removeSync(codePath);

// 如果在同一区域,使用内网地址加速
let finalUrl = codeUrl;
if (process.env.FC_REGION === region) {
finalUrl = codeUrl.replace(".aliyuncs.com", "-internal.aliyuncs.com");
}

await downloads(finalUrl, {
dest: codePath,
extract: true,
});

return codePath;
}

/**
* 获取函数名称(带agentrun前缀)
*/
export function getFunctionName(runtimeId: string): string {
const functionName = `agentrun-${runtimeId}`;
return functionName;
}
Loading