diff --git a/apps/api/src/routes/__tests__/public-metadata.test.ts b/apps/api/src/routes/__tests__/public-metadata.test.ts index 6850f312..faaa8960 100644 --- a/apps/api/src/routes/__tests__/public-metadata.test.ts +++ b/apps/api/src/routes/__tests__/public-metadata.test.ts @@ -1,5 +1,5 @@ import { Hono } from 'hono' -import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest' vi.mock('../../db/client.js', () => ({ db: { @@ -18,6 +18,11 @@ vi.mock('../../db/schema.js', () => ({ }, })) +const isOauthChannelConfigured = vi.fn() +vi.mock('../../lib/oidc.js', () => ({ + isOauthChannelConfigured: () => isOauthChannelConfigured(), +})) + import { db } from '../../db/client.js' import { asyncQuery } from '../../test/async-query.js' @@ -39,6 +44,7 @@ describe('Public metadata routes', () => { beforeEach(async () => { vi.clearAllMocks() + isOauthChannelConfigured.mockResolvedValue(true) const mod = await import('../public-metadata.js') app = new Hono() app.route('/api/public', mod.default) @@ -128,6 +134,41 @@ describe('Public metadata routes', () => { }) }) + it('reports OAuth as disabled when the platform channel is not configured', async () => { + isOauthChannelConfigured.mockResolvedValue(false) + ;(db.select as Mock).mockReturnValue( + selectChain([ + { + id: 'agt_1', + name: 'Agent One', + description: 'desc', + publishStatus: 'published', + publishChannels: ['oauth'], + oauthAccessMode: 'all_idaas_users', + }, + ]), + ) + + const res = await app.request('/api/public/agents/metadata?agentId=agt_1') + + expect(res.status).toBe(200) + expect(await res.json()).toEqual({ + data: [ + { + agentId: 'agt_1', + exists: true, + metadata: { + name: 'Agent One', + description: 'desc', + oauthEnabled: false, + oauthAccessMode: 'all_idaas_users', + }, + }, + ], + }) + expect(isOauthChannelConfigured).toHaveBeenCalledTimes(1) + }) + it('hides missing, draft, and stopped agents behind exists=false', async () => { ;(db.select as Mock).mockReturnValue( selectChain([ diff --git a/apps/api/src/routes/public-metadata.ts b/apps/api/src/routes/public-metadata.ts index 26f78a1f..68fab0f9 100644 --- a/apps/api/src/routes/public-metadata.ts +++ b/apps/api/src/routes/public-metadata.ts @@ -3,6 +3,7 @@ import { Hono } from 'hono' import { db } from '../db/client.js' import { agents } from '../db/schema.js' import { normalizeOauthAccessMode } from '../lib/gateway-auth-errors.js' +import { isOauthChannelConfigured } from '../lib/oidc.js' const MAX_AGENT_IDS = 50 @@ -26,22 +27,25 @@ app.get('/agents/metadata', async (c) => { return c.json({ error: `agentIds supports at most ${MAX_AGENT_IDS} IDs` }, 400) } - const rows = await db - .select({ - id: agents.id, - name: agents.name, - description: agents.description, - publishStatus: agents.publishStatus, - publishChannels: agents.publishChannels, - oauthAccessMode: agents.oauthAccessMode, - }) - .from(agents) - .where(inArray(agents.id, requestedIds)) + const [rows, oauthChannelConfigured] = await Promise.all([ + db + .select({ + id: agents.id, + name: agents.name, + description: agents.description, + publishStatus: agents.publishStatus, + publishChannels: agents.publishChannels, + oauthAccessMode: agents.oauthAccessMode, + }) + .from(agents) + .where(inArray(agents.id, requestedIds)), + isOauthChannelConfigured(), + ]) const byId = new Map(rows.map((agent) => [agent.id, agent])) const data = requestedIds.map((agentId) => { const agent = byId.get(agentId) - if (!agent || agent.publishStatus !== 'published') { + if (agent?.publishStatus !== 'published') { return { agentId, exists: false, metadata: null } } @@ -52,7 +56,7 @@ app.get('/agents/metadata', async (c) => { metadata: { name: agent.name, description: agent.description ?? '', - oauthEnabled: publishChannels.includes('oauth'), + oauthEnabled: publishChannels.includes('oauth') && oauthChannelConfigured, oauthAccessMode: normalizeOauthAccessMode(agent.oauthAccessMode), }, } diff --git a/apps/web/src/content/manual/en/12-triggers.md b/apps/web/src/content/manual/en/12-triggers.md index 27e18795..fe2c4703 100644 --- a/apps/web/src/content/manual/en/12-triggers.md +++ b/apps/web/src/content/manual/en/12-triggers.md @@ -146,13 +146,13 @@ If an external system only needs to display public info about published Agents, curl ".../api/public/agents/metadata?agentIds=agt_1,agt_2" ``` -The returned fields include the Agent name, the description from its config page, whether the OAuth channel is enabled, and the OAuth access mode: +The returned fields include the Agent name, the description from its config page, whether the OAuth channel is currently available, and the OAuth access mode: | Field | Description | |------|------| | `name` | Agent name | | `description` | Description from the Agent's config page | -| `oauthEnabled` | Whether the publish channels include OAuth | +| `oauthEnabled` | `true` when the Agent publishes OAuth and the platform has enterprise OIDC plus a non-empty OAuth channel audience configured; `false` when the Agent switch is on but the platform cannot yet verify callers | | `oauthAccessMode` | `all_idaas_users` means all enterprise users; `specified_users` means only the addresses on the allowlist | Agents that are unpublished, stopped, or nonexistent uniformly return `exists: false`, without exposing draft information. diff --git a/apps/web/src/content/manual/zh/12-triggers.md b/apps/web/src/content/manual/zh/12-triggers.md index 8ea27d87..fc0a19e4 100644 --- a/apps/web/src/content/manual/zh/12-triggers.md +++ b/apps/web/src/content/manual/zh/12-triggers.md @@ -140,13 +140,13 @@ OAuth 调用地址是 `POST /api/oauth/:agentId/invoke`。请求头携带调用 curl ".../api/public/agents/metadata?agentIds=agt_1,agt_2" ``` -返回字段包括 Agent 名称、配置页描述、是否开启 OAuth 渠道,以及 OAuth 访问范围: +返回字段包括 Agent 名称、配置页描述、OAuth 渠道当前是否可用,以及 OAuth 访问范围: | 字段 | 说明 | |------|------| | `name` | Agent 名称 | | `description` | Agent 配置页的描述 | -| `oauthEnabled` | 发布渠道是否包含 OAuth | +| `oauthEnabled` | Agent 已发布 OAuth 渠道,且平台已配置企业 OIDC 与非空的 OAuth 渠道受众时为 `true`;只开启 Agent 渠道但平台尚不能验签时为 `false` | | `oauthAccessMode` | `all_idaas_users` 表示全体企业用户;`specified_users` 表示仅名单内的指定企业用户 | 未发布、已停止或不存在的 Agent 会统一返回 `exists: false`,不会暴露草稿信息。