diff --git a/docs/commands.md b/docs/commands.md index ba31086..198a46d 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1217,16 +1217,17 @@ Proxy a provider API request through a connected connector app. - Arguments: `` is the service name. - Options: `-d, --data ` accepts a complete proxy request JSON object or `@path` to a JSON file. The object shape is - `{ endpoint, method, query?, headers?, body? }`. + `{ endpoint, method?, query?, headers?, body? }`. - Options: `--input ` is an alias for `--data `. -- Options: without `--data`, use `--endpoint ` and - `--method ` plus optional `--query `, `--headers `, and +- Options: without `--data`, use `--endpoint ` plus optional + `--method `, `--query `, `--headers `, and `--body ` to build the same request object. The `--data` form cannot be combined with these split request options. - Options: `--endpoint` is a provider endpoint path relative to the provider proxy base URL, or an allowed absolute HTTPS URL. - Options: `--method` must be one of `GET`, `POST`, `PUT`, `PATCH`, or - `DELETE`. Values are case-insensitive. + `DELETE`. Values are case-insensitive. Like `curl`, the method defaults to + `GET` when omitted, in both the split form and the `--data` object. - Options: `--query` must be a JSON object whose values are strings, numbers, booleans, or `null`. - Options: `--headers` must be a JSON object with string values. diff --git a/docs/commands.zh-CN.md b/docs/commands.zh-CN.md index 06bcc2c..80e0cc6 100644 --- a/docs/commands.zh-CN.md +++ b/docs/commands.zh-CN.md @@ -1010,15 +1010,16 @@ CLI 默认记录受隐私约束的命令使用 telemetry。事件不包含 free- - 参数:`` 为服务名。 - 选项:`-d, --data ` 接收完整 proxy request JSON object,或使用 `@路径` 读取 JSON 文件。对象形状为 - `{ endpoint, method, query?, headers?, body? }`。 + `{ endpoint, method?, query?, headers?, body? }`。 - 选项:`--input ` 是 `--data ` 的 alias。 -- 选项:未传 `--data` 时,使用 `--endpoint ` 和 - `--method `,以及可选的 `--query `、`--headers `、 +- 选项:未传 `--data` 时,使用 `--endpoint `,以及可选的 + `--method `、`--query `、`--headers `、 `--body ` 组装同样的 request object。`--data` 形式不能与这些拆分 request 选项同时使用。 - 选项:`--endpoint` 是相对于 provider proxy base URL 的 provider endpoint path,或允许的绝对 HTTPS URL。 - 选项:`--method` 必须是 `GET`、`POST`、`PUT`、`PATCH` 或 `DELETE`,大小写不敏感。 + 与 `curl` 一样,省略时默认为 `GET`,拆分形式和 `--data` 对象均适用。 - 选项:`--query` 必须是 JSON object,值只能是 string、number、boolean 或 `null`。 - 选项:`--headers` 必须是 string 值的 JSON object。认证 header 会由 diff --git a/src/application/commands/connector/index.cli.test.ts b/src/application/commands/connector/index.cli.test.ts index efe1f2b..0260f58 100644 --- a/src/application/commands/connector/index.cli.test.ts +++ b/src/application/commands/connector/index.cli.test.ts @@ -836,6 +836,117 @@ describe("connectorCommand CLI", () => { } }); + test("defaults the connector proxy method to GET when --method is omitted", async () => { + const sandbox = await createCliSandbox(); + + try { + await writeAuthFile(sandbox); + + const requests: Request[] = []; + const result = await sandbox.run( + [ + "connector", + "proxy", + "tavily", + "--endpoint", + "/search", + "--query", + "{\"limit\":1}", + "--json", + ], + { + fetcher: async (input, init) => { + requests.push(toRequest(input, init)); + + return new Response(JSON.stringify({ + data: { + data: null, + headers: {}, + status: 200, + }, + meta: { + executionId: "exec-1", + service: "tavily", + }, + })); + }, + }, + ); + + expect(result.exitCode).toBe(0); + expect(result.stderr).toBe(""); + expect(requests).toHaveLength(1); + await expect(requests[0]?.json()).resolves.toEqual({ + endpoint: "/search", + method: "GET", + query: { limit: 1 }, + }); + const telemetryPayload = parseTelemetryRowPayload( + readTelemetryRowsForTest( + join(sandbox.env.XDG_CONFIG_HOME!, APP_NAME, "telemetry"), + )[0]!, + ); + expect(telemetryPayload).toMatchObject({ + properties: { + command_full: "connector.proxy", + has_body: false, + method: "GET", + }, + }); + } + finally { + await sandbox.cleanup(); + } + }); + + test("defaults the connector proxy method to GET when --data omits method", async () => { + const sandbox = await createCliSandbox(); + + try { + await writeAuthFile(sandbox); + + const requests: Request[] = []; + const result = await sandbox.run( + [ + "connector", + "proxy", + "tavily", + "--data", + "{\"endpoint\":\"/search\"}", + "--json", + ], + { + fetcher: async (input, init) => { + requests.push(toRequest(input, init)); + + return new Response(JSON.stringify({ + data: { + data: null, + headers: {}, + status: 200, + }, + meta: { + executionId: "exec-1", + service: "tavily", + }, + })); + }, + }, + ); + + expect(result.exitCode).toBe(0); + expect(result.stderr).toBe(""); + expect(requests).toHaveLength(1); + await expect(requests[0]?.json()).resolves.toEqual({ + endpoint: "/search", + method: "GET", + }); + } + finally { + await sandbox.cleanup(); + } + }); + test("rejects invalid connector proxy method values before login", async () => { const sandbox = await createCliSandbox(); @@ -874,10 +985,6 @@ describe("connectorCommand CLI", () => { argv: ["connector", "proxy", "tavily"], message: "The --endpoint option is required when --data is omitted.", }, - { - argv: ["connector", "proxy", "tavily", "--endpoint", "/search"], - message: "The --method option is required when --data is omitted.", - }, { argv: [ "connector", diff --git a/src/application/commands/connector/proxy.ts b/src/application/commands/connector/proxy.ts index 185081a..7ba63a6 100644 --- a/src/application/commands/connector/proxy.ts +++ b/src/application/commands/connector/proxy.ts @@ -38,7 +38,8 @@ const proxyRequestSchema = z.object({ body: z.unknown().optional(), endpoint: z.string().trim().min(1), headers: z.record(z.string(), z.string()).optional(), - method: connectorProxyMethodSchema, + // Match curl: a request without an explicit method is a GET. + method: connectorProxyMethodSchema.default("GET"), query: z.record(z.string(), proxyQueryValueSchema).optional(), }).strict(); @@ -179,13 +180,9 @@ async function buildConnectorProxyRequest( throw new CliUserError("errors.connectorProxy.endpointRequired", 2); } - if (input.method === undefined) { - throw new CliUserError("errors.connectorProxy.methodRequired", 2); - } - return parseProxyRequest({ endpoint: input.endpoint, - method: input.method, + ...(input.method !== undefined ? { method: input.method } : {}), ...(input.query !== undefined ? { query: parseJsonOption(input.query, "errors.connectorProxy.invalidQueryJson") } : {}), diff --git a/src/i18n/catalog.ts b/src/i18n/catalog.ts index 0da9b9d..7b7f243 100644 --- a/src/i18n/catalog.ts +++ b/src/i18n/catalog.ts @@ -491,8 +491,6 @@ export const enMessages = { "The --query value is not valid JSON: {message}", "errors.connectorProxy.invalidResponse": "The connector proxy response body is unsupported.", - "errors.connectorProxy.methodRequired": - "The --method option is required when --data is omitted.", "errors.connectorProxy.requestError": "The connector proxy request failed: {message}", "errors.connectorProxy.requestFailed": @@ -1018,7 +1016,7 @@ export const enMessages = { "options.connectorProxyHeaders": "Specify non-authentication upstream headers as a JSON object", "options.connectorProxyMethod": - "Specify the upstream HTTP method", + "Specify the upstream HTTP method (default: GET)", "options.connectorProxyTeam": "Run the proxy request under the given team identity", "options.connectorProxyQuery": @@ -1851,8 +1849,6 @@ export const zhMessages = { "--query 的值不是有效 JSON:{message}", "errors.connectorProxy.invalidResponse": "Connector proxy 响应内容不受支持。", - "errors.connectorProxy.methodRequired": - "省略 --data 时必须传入 --method。", "errors.connectorProxy.requestError": "Connector proxy 请求失败:{message}", "errors.connectorProxy.requestFailed": @@ -2373,7 +2369,7 @@ export const zhMessages = { "options.connectorProxyHeaders": "以 JSON object 指定非认证上游请求头", "options.connectorProxyMethod": - "指定上游 HTTP method", + "指定上游 HTTP method (默认 GET)", "options.connectorProxyTeam": "以指定团队身份运行该 proxy 请求", "options.connectorProxyQuery":