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
9 changes: 5 additions & 4 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -1217,16 +1217,17 @@ Proxy a provider API request through a connected connector app.
- Arguments: `<serviceName>` is the service name.
- Options: `-d, --data <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 <data>` is an alias for `--data <data>`.
- Options: without `--data`, use `--endpoint <endpoint>` and
`--method <method>` plus optional `--query <json>`, `--headers <json>`, and
- Options: without `--data`, use `--endpoint <endpoint>` plus optional
`--method <method>`, `--query <json>`, `--headers <json>`, and
`--body <json>` 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.
Expand Down
7 changes: 4 additions & 3 deletions docs/commands.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -1010,15 +1010,16 @@ CLI 默认记录受隐私约束的命令使用 telemetry。事件不包含 free-
- 参数:`<serviceName>` 为服务名。
- 选项:`-d, --data <data>` 接收完整 proxy request JSON object,或使用
`@路径` 读取 JSON 文件。对象形状为
`{ endpoint, method, query?, headers?, body? }`。
`{ endpoint, method?, query?, headers?, body? }`。
- 选项:`--input <data>` 是 `--data <data>` 的 alias。
- 选项:未传 `--data` 时,使用 `--endpoint <endpoint>`
`--method <method>`,以及可选的 `--query <json>`、`--headers <json>`、
- 选项:未传 `--data` 时,使用 `--endpoint <endpoint>`,以及可选的
`--method <method>``--query <json>`、`--headers <json>`、
`--body <json>` 组装同样的 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 会由
Expand Down
115 changes: 111 additions & 4 deletions src/application/commands/connector/index.cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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",
Expand Down
9 changes: 3 additions & 6 deletions src/application/commands/connector/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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") }
: {}),
Expand Down
8 changes: 2 additions & 6 deletions src/i18n/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -2373,7 +2369,7 @@ export const zhMessages = {
"options.connectorProxyHeaders":
"以 JSON object 指定非认证上游请求头",
"options.connectorProxyMethod":
"指定上游 HTTP method",
"指定上游 HTTP method (默认 GET)",
"options.connectorProxyTeam":
"以指定团队身份运行该 proxy 请求",
"options.connectorProxyQuery":
Expand Down