Skip to content

Commit 46c6d95

Browse files
committed
fix(repo): 修复用量查询配置和工具链兼容性
1 parent 6fd35b6 commit 46c6d95

10 files changed

Lines changed: 195 additions & 10 deletions

File tree

‎apps/server/src/routes/usage.ts‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { createRoute, OpenAPIHono } from "@hono/zod-openapi";
2-
import { listModelUsage, listToolUsage } from "@openagentpack/sdk";
2+
import { listModelUsage, listToolUsage, readProjectRuntime } from "@openagentpack/sdk";
33
import { errorResponses } from "@/schemas/common";
44
import {
55
ListModelUsageRequestSchema,
66
ListToolUsageRequestSchema,
77
ModelUsagePageSchema,
88
ToolUsagePageSchema,
99
} from "@/schemas/usage";
10-
import { DEFAULT_AGENT_ID } from "@/services/agents/catalog";
11-
import { withAgentRuntime } from "@/services/runtime-factory";
10+
import { projectRuntimeManager } from "@/services/project-manager";
1211

1312
export const usageRoute = new OpenAPIHono();
1413

@@ -33,7 +32,8 @@ const listModelUsageRoute = createRoute({
3332

3433
usageRoute.openapi(listModelUsageRoute, async (c) => {
3534
const request = c.req.valid("json");
36-
const page = await withAgentRuntime(DEFAULT_AGENT_ID, async (ctx) => {
35+
await projectRuntimeManager.ensureStarted();
36+
const page = await readProjectRuntime(projectRuntimeManager.requireRuntimeInput(), (ctx) => {
3737
const provider = ctx.config.defaults?.provider;
3838
return listModelUsage(ctx, request, { provider });
3939
});
@@ -58,7 +58,8 @@ const listToolUsageRoute = createRoute({
5858

5959
usageRoute.openapi(listToolUsageRoute, async (c) => {
6060
const request = c.req.valid("json");
61-
const page = await withAgentRuntime(DEFAULT_AGENT_ID, async (ctx) => {
61+
await projectRuntimeManager.ensureStarted();
62+
const page = await readProjectRuntime(projectRuntimeManager.requireRuntimeInput(), (ctx) => {
6263
const provider = ctx.config.defaults?.provider;
6364
return listToolUsage(ctx, request, { provider });
6465
});
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
2+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
3+
import { tmpdir } from "node:os";
4+
import { join } from "node:path";
5+
import { setDefaultFetch } from "@openagentpack/sdk";
6+
import { initializeDirectoryProject } from "@openagentpack/sdk/project-workspace";
7+
import { app } from "@/app";
8+
import { ProjectRuntimeManager, projectRuntimeManager } from "@/services/project-manager";
9+
10+
const environmentKeys = [
11+
"DASHSCOPE_API_KEY",
12+
"BAILIAN_API_KEY",
13+
"BAILIAN_BASE_URL",
14+
"BAILIAN_WORKSPACE_ID",
15+
"AGENTS_PROVIDER",
16+
"AGENTS_PLAYGROUND_TOKEN",
17+
] as const;
18+
let originalEnvironment: Map<string, string | undefined>;
19+
let directory: string;
20+
let manager: ProjectRuntimeManager;
21+
const spies: Array<{ mockRestore(): void }> = [];
22+
const page = { items: [], total: 0, total_call_count: 0, page_no: 1, page_size: 20, data_updated_at: 0 };
23+
24+
beforeEach(async () => {
25+
originalEnvironment = new Map(environmentKeys.map((key) => [key, process.env[key]]));
26+
for (const key of environmentKeys) delete process.env[key];
27+
directory = await mkdtemp(join(tmpdir(), "openagentpack-usage-route-"));
28+
await initializeDirectoryProject({ projectRoot: directory });
29+
await writeFile(
30+
join(directory, ".env"),
31+
"DASHSCOPE_API_KEY=project-test-key\nBAILIAN_BASE_URL=https://project-provider.test/api/v1/agentstudio\n",
32+
);
33+
manager = new ProjectRuntimeManager(directory);
34+
await manager.ensureStarted();
35+
expect(manager.getSnapshot().status).toBe("valid");
36+
spies.push(
37+
spyOn(projectRuntimeManager, "ensureStarted").mockImplementation(() => manager.ensureStarted()),
38+
spyOn(projectRuntimeManager, "requireRuntimeInput").mockImplementation(() => manager.requireRuntimeInput()),
39+
);
40+
});
41+
42+
afterEach(async () => {
43+
for (const spy of spies.splice(0)) spy.mockRestore();
44+
manager?.close();
45+
setDefaultFetch(undefined);
46+
for (const [key, value] of originalEnvironment) {
47+
if (value === undefined) delete process.env[key];
48+
else process.env[key] = value;
49+
}
50+
if (directory) await rm(directory, { recursive: true, force: true });
51+
});
52+
53+
function usageRequest(path: string) {
54+
return app.request(path, {
55+
method: "POST",
56+
headers: { "Content-Type": "application/json" },
57+
body: JSON.stringify({ scope: { type: "session", session_id: "test-session" } }),
58+
});
59+
}
60+
61+
describe("Workbench usage Provider selection", () => {
62+
for (const [resource, processCredentials] of [
63+
["models", false],
64+
["tools", true],
65+
] as const) {
66+
test(`${resource} uses project-local credentials when process credentials are ${processCredentials ? "different" : "absent"}`, async () => {
67+
if (processCredentials) {
68+
process.env.DASHSCOPE_API_KEY = "environment-test-key";
69+
process.env.BAILIAN_BASE_URL = "https://environment-provider.test/api/v1/agentstudio";
70+
process.env.AGENTS_PROVIDER = "claude";
71+
}
72+
const calls: Array<{ url: string; authorization: string | null }> = [];
73+
setDefaultFetch(async (input, init) => {
74+
calls.push({
75+
url: input instanceof Request ? input.url : input.toString(),
76+
authorization: new Headers(init?.headers).get("Authorization"),
77+
});
78+
return Response.json({ data: page });
79+
});
80+
81+
const response = await usageRequest(`/api/usage/${resource}`);
82+
83+
expect(response.status).toBe(200);
84+
expect(await response.json()).toEqual(page);
85+
expect(calls).toEqual([
86+
{
87+
url: `https://project-provider.test/api/v1/agentstudio/usage/${resource}`,
88+
authorization: "Bearer project-test-key",
89+
},
90+
]);
91+
});
92+
}
93+
94+
test("rejects a missing project instead of querying with process credentials", async () => {
95+
await rm(join(directory, "project.json"));
96+
await manager.refreshAfterSourceMutation();
97+
expect(manager.getSnapshot().status).toBe("missing");
98+
process.env.DASHSCOPE_API_KEY = "environment-test-key";
99+
process.env.BAILIAN_BASE_URL = "https://environment-provider.test/api/v1/agentstudio";
100+
let requests = 0;
101+
setDefaultFetch(async () => {
102+
requests += 1;
103+
return Response.json({ data: page });
104+
});
105+
106+
const response = await usageRequest("/api/usage/models");
107+
108+
expect(response.status).toBe(422);
109+
expect(await response.json()).toMatchObject({ error: { message: expect.stringContaining("missing") } });
110+
expect(requests).toBe(0);
111+
});
112+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { expect, test } from "bun:test";
2+
import { createRequire } from "node:module";
3+
import openapiTS, { astToString } from "openapi-typescript";
4+
5+
const require = createRequire(import.meta.url);
6+
const requireOpenapi = createRequire(require.resolve("openapi-typescript"));
7+
const { createConfig } = requireOpenapi("@redocly/openapi-core");
8+
9+
test("OpenAPI generation applies matching HTTP headers to a remote schema", async () => {
10+
const calls: Array<{ url: string; headers: Headers }> = [];
11+
const redocly = await createConfig({
12+
resolve: {
13+
http: {
14+
headers: [
15+
{ matches: "https://schema.test/**", name: "Authorization", value: "Bearer schema-test-key" },
16+
{ matches: "https://other.test/**", name: "X-Other-Key", value: "other-test-key" },
17+
],
18+
},
19+
},
20+
});
21+
redocly.resolve.http.customFetch = async (url: string, init?: RequestInit) => {
22+
calls.push({ url, headers: new Headers(init?.headers) });
23+
return Response.json({
24+
openapi: "3.0.0",
25+
info: { title: "Remote schema", version: "1.0.0" },
26+
paths: {},
27+
components: {
28+
schemas: {
29+
User: { type: "object", required: ["name"], properties: { name: { type: "string" } } },
30+
},
31+
},
32+
});
33+
};
34+
35+
const output = astToString(await openapiTS(new URL("https://schema.test/openapi.json"), { redocly }));
36+
37+
expect(calls).toHaveLength(1);
38+
expect(calls[0]?.url).toBe("https://schema.test/openapi.json");
39+
expect(calls[0]?.headers.get("Authorization")).toBe("Bearer schema-test-key");
40+
expect(calls[0]?.headers.has("X-Other-Key")).toBe(false);
41+
expect(output).toContain("name: string");
42+
});

‎bun.lock‎

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎docs/contributing/development.md‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ How to set up the repo, run the CLI/WebUI from source, run verification, and res
44

55
## Prerequisites
66

7-
- [Bun](https://bun.sh) `1.3.5` — the pinned workspace runtime and package manager.
7+
- [Bun](https://bun.sh) `1.4.4` — the pinned workspace runtime and package manager, matching CI.
8+
9+
Upgrade Bun before installing dependencies: the committed v2 lockfile cannot be read by Bun 1.3.
810

911
## Setup
1012

@@ -15,6 +17,12 @@ git remote add upstream https://github.com/modelstudioai/OpenAgentPack.git
1517
bun install
1618
```
1719

20+
`minimatch` stays pinned to the security-fixed 11.x release. The Bun patch for
21+
`@redocly/openapi-core@1.34.22` adapts its CommonJS import to the named `minimatch`
22+
export, preserving remote OpenAPI header matching without downgrading to a vulnerable
23+
5.x release. When upgrading Redocly, verify `apps/webui/tests/openapi-remote.test.ts`
24+
and remove the patch once the upstream import is compatible.
25+
1826
Run the CLI from source without a global install:
1927

2028
```sh
@@ -88,4 +96,4 @@ OpenAgentPack is a Bun workspace. Packages resolve each other's TypeScript sourc
8896
- Changing the `@openagentpack/sdk` / `@openagentpack/cli` publish model (compiled `dist/` published to public npm) without a proposal.
8997
- New runtime dependencies without justification.
9098

91-
See the top-level [CONTRIBUTING.md](../../CONTRIBUTING.md) for the merge requirements, and [Provider development](./provider-development.md) and [Release](./release.md) for the deeper workflows.
99+
See the top-level [CONTRIBUTING.md](../../CONTRIBUTING.md) for the merge requirements, and [Provider development](./provider-development.md) and [Release](./release.md) for the deeper workflows.

‎docs/getting-started.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This guide takes you from zero to a running agent session in a few minutes. It u
99
- Qoder — `QODER_PAT`
1010
- Bailian (Aliyun AgentStudio) — `DASHSCOPE_API_KEY` and `BAILIAN_WORKSPACE_ID`
1111
- Volcengine Ark (Managed Agents) — `ARK_API_KEY`
12-
- To run the project from source: [Bun](https://bun.sh) `1.3.5` (see [Development](./contributing/development.md)). To use the published CLI, Node.js or Bun is enough.
12+
- To run the project from source: [Bun](https://bun.sh) `1.4.4` (see [Development](./contributing/development.md)). To use the published CLI, Node.js or Bun is enough.
1313

1414
## Install
1515

‎docs/getting-started.zh-CN.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
- Qoder — `QODER_PAT`
1010
- 百炼(阿里云百炼 AgentStudio)— `DASHSCOPE_API_KEY` 与 `BAILIAN_WORKSPACE_ID`
1111
- 火山方舟(火山引擎方舟 Managed Agents)— `ARK_API_KEY`
12-
- 从源码运行需要 [Bun](https://bun.sh) `1.3.5`(见 [开发环境](./contributing/development.md));使用发布的 CLI 只需 Node.js 或 Bun。
12+
- 从源码运行需要 [Bun](https://bun.sh) `1.4.4`(见 [开发环境](./contributing/development.md));使用发布的 CLI 只需 Node.js 或 Bun。
1313

1414
## 安装
1515

‎docs/reference/openapi.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,7 @@ bun run dev # server + webui together
5656
```
5757

5858
The server reads the same provider credentials as the CLI (`.env` and `~/.agents/config.json`); see [Provider reference](./providers.md).
59+
60+
用量接口使用当前目录项目解析后的 Provider 配置和项目本地 `.env`,与项目 Session 使用相同的配置来源。
61+
通过 `AGENTS_PROJECT_ROOT` 指定项目目录时,无需另行设置进程级 `AGENTS_PROVIDER` 或复制凭据。
62+
缺失或无效的项目返回 `422`,不会回退到进程环境中的其他 Provider。

‎package.json‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"bugs": {
1212
"url": "https://github.com/modelstudioai/OpenAgentPack/issues"
1313
},
14-
"packageManager": "bun@1.3.5",
14+
"packageManager": "bun@1.4.4",
1515
"workspaces": [
1616
"apps/*",
1717
"packages/*"
@@ -87,6 +87,9 @@
8787
"prosemirror-view": "1.42.3",
8888
"source-map-js": "1.2.2"
8989
},
90+
"patchedDependencies": {
91+
"@redocly/openapi-core@1.34.22": "patches/@redocly%2Fopenapi-core@1.34.22.patch"
92+
},
9093
"trustedDependencies": [
9194
"@ast-grep/cli"
9295
]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
diff --git a/lib/utils.js b/lib/utils.js
2+
--- a/lib/utils.js
3+
+++ b/lib/utils.js
4+
@@ -39,7 +39,7 @@ exports.getProxyAgent = getProxyAgent;
5+
exports.dequal = dequal;
6+
const fs = require("fs");
7+
const path_1 = require("path");
8+
-const minimatch = require("minimatch");
9+
+const { minimatch } = require("minimatch");
10+
const js_yaml_1 = require("./js-yaml");
11+
const env_1 = require("./env");
12+
const logger_1 = require("./logger");

0 commit comments

Comments
 (0)