Skip to content

Commit 4d8a9be

Browse files
authored
Add declarative identity and channel resources (#42)
* Add declarative identity and channel resources Change-Id: I2c8c162fbe08f4a53b79d3d66df7fdf9cd44df23 * Update OpenAPI resource enum Change-Id: Ief4ff53e264674324d6b55b7ed8f344dc0509d26 * Regenerate WebUI API types Change-Id: Id8cdde41273b8abfe99c6add75ad2f2608eb70cf
1 parent ddef91c commit 4d8a9be

34 files changed

Lines changed: 1069 additions & 179 deletions

apps/server/openapi.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,9 @@
721721
"agent",
722722
"template",
723723
"deployment",
724-
"file"
724+
"file",
725+
"identity",
726+
"channel"
725727
]
726728
},
727729
"name": {
@@ -752,7 +754,9 @@
752754
"agent",
753755
"template",
754756
"deployment",
755-
"file"
757+
"file",
758+
"identity",
759+
"channel"
756760
]
757761
},
758762
"name": {
@@ -787,7 +791,9 @@
787791
"agent",
788792
"template",
789793
"deployment",
790-
"file"
794+
"file",
795+
"identity",
796+
"channel"
791797
]
792798
},
793799
"name": {
@@ -843,7 +849,9 @@
843849
"agent",
844850
"template",
845851
"deployment",
846-
"file"
852+
"file",
853+
"identity",
854+
"channel"
847855
]
848856
},
849857
"name": {

apps/webui/src/lib/api/generated/schema.d.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,9 @@ export interface paths {
283283
| "agent"
284284
| "template"
285285
| "deployment"
286-
| "file";
286+
| "file"
287+
| "identity"
288+
| "channel";
287289
name: string;
288290
provider: string;
289291
};
@@ -298,7 +300,9 @@ export interface paths {
298300
| "agent"
299301
| "template"
300302
| "deployment"
301-
| "file";
303+
| "file"
304+
| "identity"
305+
| "channel";
302306
name: string;
303307
provider: string;
304308
}[];
@@ -315,7 +319,9 @@ export interface paths {
315319
| "agent"
316320
| "template"
317321
| "deployment"
318-
| "file";
322+
| "file"
323+
| "identity"
324+
| "channel";
319325
name: string;
320326
provider: string;
321327
};
@@ -341,7 +347,9 @@ export interface paths {
341347
| "agent"
342348
| "template"
343349
| "deployment"
344-
| "file";
350+
| "file"
351+
| "identity"
352+
| "channel";
345353
name: string;
346354
provider: string;
347355
}[];

docs/examples.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The [`examples/`](../examples) directory has runnable configs for every provider
1212
| Connect an MCP server + vault | [`examples/claude/with-mcp/`](../examples/claude/with-mcp/) · [`examples/qoder/with-mcp/`](../examples/qoder/with-mcp/) · [`examples/ark/with-mcp/`](../examples/ark/with-mcp/) |
1313
| Use an official MCP server | [`examples/bailian/with-mcp/`](../examples/bailian/with-mcp/) |
1414
| Use a vault | [`examples/bailian/with-vault/`](../examples/bailian/with-vault/) · [`examples/qoder/with-vault/`](../examples/qoder/with-vault/) |
15+
| Connect a credential-based IM Channel | [`examples/qoder/with-channel/`](../examples/qoder/with-channel/) |
1516
| Use memory stores | [`examples/qoder/with-memory/`](../examples/qoder/with-memory/) · [`examples/ark/full/`](../examples/ark/full/) |
1617
| Upload local files (Files API) | [`examples/bailian/with-files/`](../examples/bailian/with-files/) · [`examples/ark/with-files/`](../examples/ark/with-files/) |
1718
| Coordinate multiple agents | [`examples/claude/multiagent/`](../examples/claude/multiagent/) · [`examples/ark/multiagent/`](../examples/ark/multiagent/) |

docs/guides/deploy-to-qoder.md

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ providers:
2222

2323
| Feature | Tier |
2424
|---------|:----:|
25-
| Environment, Vault, Skill, Agent, MCP Server, Memory Store, Deployment, Session | native |
25+
| Environment, Vault, Skill, Agent, MCP Server, Memory Store, Deployment, Session, Identity, Channel | native |
2626
| Multi-Agent | unsupported |
2727

2828
A `deployment run` on Qoder creates a native Deployment Run and associated Session. Cron schedules run server-side.
@@ -94,16 +94,18 @@ agents:
9494
`agents plan/apply/destroy` then manage a Qoder Forward Template (`tmpl_...`) instead of a Managed Agent
9595
(`agent_...`). The default remains Managed Agent delivery when `delivery` is omitted.
9696

97-
Identity is optional for local Forward Session testing. If omitted, OpenCMA finds the enabled Qoder Identity whose
98-
`external_id` is `__qca_admin_identity__`, then sends its real `idn_...` id to the Session API. To run as a specific
99-
user—or when the reserved Identity is unavailable—configure an existing Identity explicitly:
97+
Forward Identity represents an end user in the integrating product. Declare it once by its stable business id and select
98+
it as the project default:
10099

101100
```yaml
102101
defaults:
103102
provider: qoder
104-
session:
105-
qoder:
106-
identity_id: idn_xxx
103+
identity: chen
104+
105+
identities:
106+
chen:
107+
external_id: user_456
108+
name: Chen
107109
```
108110

109111
Then use the same session command to test a Forward-delivered agent:
@@ -112,14 +114,25 @@ Then use the same session command to test a Forward-delivered agent:
112114
agents session run "Hello" --agent forward-assistant
113115
```
114116

115-
The CLI resolves the applied Template and optional configured Identity, creates a Forward Session, and routes create, send,
116-
stream, get, and archive operations through the Forward gateway. It never creates or updates an Identity as a side effect
117-
of starting a Session. Use `--identity-id idn_xxx` to override the YAML default for one invocation. Managed sessions keep
118-
using the Cloud gateway.
117+
`agents apply` manages the declared Identity. The CLI resolves `defaults.identity` through state, creates a Forward
118+
Session, and routes create, send, stream, get, and archive operations through the Forward gateway. Use
119+
`--identity-id idn_xxx` only as a runtime escape hatch. Managed sessions keep using the Cloud gateway.
120+
121+
Credential-based messaging Channels are declarative too:
122+
123+
```yaml
124+
channels:
125+
support-dingtalk:
126+
agent: forward-assistant
127+
type: dingtalk
128+
credentials:
129+
client_id: ${DINGTALK_CLIENT_ID}
130+
client_secret: ${DINGTALK_CLIENT_SECRET}
131+
```
119132

120-
Production messaging delivery is a separate Forward Channel concern. An IM Channel explicitly binds a business Identity
121-
and Template; do not treat the CLI's local testing Identity as an end-user Identity. Qoder
122-
recommends QR-code authorization for Channel binding so channel credentials do not need to be stored locally.
133+
The Channel inherits `defaults.identity`; the Qoder adapter resolves both remote ids and maps the generic declaration to
134+
the Forward Channel request. Feishu uses `app_id`/`app_secret`, and WeCom uses `bot_id`/`secret`. Personal WeChat is QR-only
135+
and is not supported by credential-based apply.
123136

124137
Forward-delivered agents still cannot be referenced by OpenAgentPack deployments; scheduled Managed Deployment runs
125138
require an Agent resource.

docs/guides/run-sessions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ A **session** is a runtime conversation started from a managed agent. Sessions a
1212
agents session run "Summarize the repo structure" --agent assistant
1313
```
1414

15-
`session run` creates a session, sends the prompt, and polls until the response completes. Pass `--stream` to receive live events over SSE. When only one agent is configured, `--agent` is auto-detected. For a Qoder agent with `delivery.qoder.type: forward`, Identity is optional: without one OpenCMA looks up the enabled Identity whose `external_id` is `__qca_admin_identity__` and sends its real `idn_...` id. Configure `defaults.session.qoder.identity_id` or pass `--identity-id` to select an existing business Identity. OpenCMA never creates or updates Identity resources implicitly.
15+
`session run` creates a session, sends the prompt, and polls until the response completes. Pass `--stream` to receive live events over SSE. When only one agent is configured, `--agent` is auto-detected. A Qoder agent with `delivery.qoder.type: forward` requires a declared Identity selected through `defaults.identity`; `agents apply` creates or resolves it before sessions run. Pass `--identity-id` only to override the resolved default with an existing provider Identity for one invocation.
1616

1717
Options:
1818

docs/reference/cli.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ Manage runtime agent sessions.
111111
| `session events <session-id>` | List event history for a session. |
112112
| `session delete <session-id>` | Delete a session. |
113113

114-
`session create` / `session run` accept `--agent`, `--identity-id`, `--environment`, `--vault`, `--memory-stores`, `--title`, and `--provider`. `--identity-id` selects an existing Qoder Forward Identity and overrides `defaults.session.qoder.identity_id`; when neither is provided, OpenCMA resolves the Identity whose `external_id` is `__qca_admin_identity__`. OpenCMA never creates or updates an Identity. `session run` and `session send` use polling by default and accept `--stream` to opt into SSE streaming, plus `--json` for JSON output. `session list` accepts `--agent` and `--all`; `session events` accepts `--limit`, `--all`, `--json`.
114+
`session create` / `session run` accept `--agent`, `--identity-id`, `--environment`, `--vault`, `--memory-stores`, `--title`, and `--provider`. Forward Sessions resolve the declared logical `defaults.identity`; `--identity-id` overrides it with an existing provider id for one invocation. `session run` and `session send` use polling by default and accept `--stream` to opt into SSE streaming, plus `--json` for JSON output. `session list` accepts `--agent` and `--all`; `session events` accepts `--limit`, `--all`, `--json`.
115115

116116
## `agents deployment`
117117

docs/reference/configuration.md

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@ version: "1"
99
providers: { <name>: <provider-config> }
1010
defaults:
1111
provider: <name> | "all"
12-
session:
13-
qoder: { identity_id: <existing-idn-id> }
12+
identity: <identity-name>
1413
environments: { <name>: EnvironmentDecl }
1514
tunnels: { <name>: TunnelDecl }
1615
vaults: { <name>: VaultDecl }
1716
memory_stores:{ <name>: MemoryStoreDecl }
1817
skills: { <name>: SkillDecl }
1918
files: { <name>: FileDecl }
19+
identities: { <name>: IdentityDecl }
2020
agents: { <name>: AgentDecl }
21+
channels: { <name>: ChannelDecl }
2122
deployments: { <name>: DeploymentDecl }
2223
```
2324
@@ -26,18 +27,67 @@ deployments: { <name>: DeploymentDecl }
2627
| `version` | string | yes | Schema version. Currently `"1"`. |
2728
| `providers` | map | yes | One block per provider; each holds its credentials. |
2829
| `defaults.provider` | string | no | Default target for `plan`/`apply`. `all` targets every declared provider. |
29-
| `defaults.session.qoder.identity_id` | string | no | Existing Qoder Forward Identity used as the local Session default. If omitted, OpenCMA looks up the enabled Identity whose `external_id` is `__qca_admin_identity__` and sends its real `idn_...` id. Never created or managed by `apply`. |
30+
| `defaults.identity` | string | no | Logical name of the default declared Identity used by identity-aware resources and Forward Sessions. |
3031
| `environments` | map | no | Cloud runtimes. |
3132
| `tunnels` | map | no | Existing Qoder BYOC tunnels referenced by sessions; OpenCMA does not manage their lifecycle. |
3233
| `vaults` | map | no | Credential stores. |
3334
| `memory_stores` | map | no | Persistent agent context (Qoder, Volcengine Ark). |
3435
| `skills` | map | no | Reusable capability modules. |
3536
| `files` | map | no | Local files uploaded to the Files API (Bailian, Volcengine Ark). |
37+
| `identities` | map | no | Stable end-user identities. Provider support is capability-gated. |
3638
| `agents` | map | no | The core managed-agent resources. |
39+
| `channels` | map | no | External messaging channels bound to an Identity and Agent. Provider support is capability-gated. |
3740
| `deployments` | map | no | Repeatable run units. |
3841

3942
Secrets use `${VAR_NAME}` and resolve from `.env` (walking up to the project root). `agents init` appends `agents.state.json` and `.env` to `.gitignore`.
4043

44+
## Identity
45+
46+
Managed identities use the integrating product's stable end-user id:
47+
48+
```yaml
49+
identities:
50+
chen:
51+
provider: qoder
52+
external_id: user_456
53+
name: Chen
54+
enabled: true
55+
metadata:
56+
department: engineering
57+
```
58+
59+
`agents apply` creates or updates the remote Identity and stores its provider id in state. To reference an Identity managed outside this project, use the mutually exclusive external-reference form:
60+
61+
```yaml
62+
identities:
63+
chen:
64+
provider: qoder
65+
identity_id: idn_019eabc123
66+
```
67+
68+
External references are verified and recorded but never updated or deleted.
69+
70+
## Channel
71+
72+
```yaml
73+
channels:
74+
support-dingtalk:
75+
provider: qoder # optional; inherits defaults.provider
76+
agent: support-agent
77+
identity: chen # optional; inherits defaults.identity
78+
type: dingtalk
79+
name: Support DingTalk # optional; defaults to the YAML key
80+
enabled: true # optional; defaults to true
81+
credentials:
82+
client_id: ${DINGTALK_CLIENT_ID}
83+
client_secret: ${DINGTALK_CLIENT_SECRET}
84+
options:
85+
include_tool_calls: false
86+
include_thinking: false
87+
```
88+
89+
The declaration intentionally uses logical `agent` and `identity` references. Provider adapters resolve remote ids and map `type`, `credentials`, and `options` to provider wire fields. Qoder Channels require the referenced Agent to use Forward delivery. Credential-based Qoder support currently covers DingTalk, Feishu, and WeCom; personal WeChat remains QR-only.
90+
4191
## Provider configuration
4292

4393
Each provider under `providers` is validated by the provider's own config schema.

examples/qoder/byoc/agents.yaml

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Qoder BYOC 端到端测试配置示例
2+
#
3+
# 用途:验证 Qoder BYOC 接入(工具调用)是否打通。
4+
# 使用前请确保已设置环境变量:
5+
# - QODER_PAT Qoder API 个人访问令牌(https://qoder.com/account/integrations)
6+
# - BYOC_ENVIRONMENT_ID 已创建的 BYOC environment id
7+
# - BYOC_TUNNEL_ID 已创建的 BYOC tunnel id
8+
# - DINGTALK_CLIENT_ID / DINGTALK_CLIENT_SECRET 钉钉渠道凭证(可选)
9+
#
10+
# 关键约束:
11+
# - environment_id / tunnel_id 指向的 BYOC 资源由平台侧开通,OpenCMA 只引用不修改。
12+
# - tunnel 只是 agents.yaml 内的命名别名,直接引用 Qoder 下发的 tunnel_id。
13+
14+
version: "1"
15+
16+
providers:
17+
qoder:
18+
api_key: ${QODER_PAT}
19+
20+
defaults:
21+
provider: qoder
22+
identity: byoc
23+
24+
# Forward Identity 表示集成侧的终端用户。OpenCMA 按 external_id 创建/复用并管理远端 Identity。
25+
identities:
26+
byoc:
27+
external_id: byoc
28+
name: "byoc"
29+
enabled: true
30+
metadata:
31+
purpose: byoc-assistant
32+
33+
channels:
34+
byoc-dingtalk:
35+
name: "byoc Agent DingTalk"
36+
agent: byoc-agent
37+
type: dingtalk
38+
enabled: true
39+
credentials:
40+
client_id: ${DINGTALK_CLIENT_ID}
41+
client_secret: ${DINGTALK_CLIENT_SECRET}
42+
options:
43+
include_tool_calls: false
44+
include_thinking: false
45+
46+
# 已存在的 BYOC 环境,通过 environment_id 直接引用,不需要 state import。
47+
environments:
48+
byoc:
49+
description: "BYOC self-hosted environment"
50+
environment_id: ${BYOC_ENVIRONMENT_ID}
51+
config:
52+
type: self_hosted
53+
metadata:
54+
purpose: byoc-test
55+
56+
# 已存在的 BYOC tunnel。直接引用 Qoder 下发的 tunnel_id,OpenCMA 不会尝试创建或修改它。
57+
tunnels:
58+
internal:
59+
description: "BYOC tunnel"
60+
tunnel_id: ${BYOC_TUNNEL_ID}
61+
62+
agents:
63+
byoc-agent:
64+
description: "Assistant running inside BYOC"
65+
model:
66+
qoder: auto
67+
instructions: |
68+
You are a BYOC end-to-end test assistant running in a self-hosted environment.
69+
Your goal is to verify that Qoder BYOC tool calling is working.
70+
Answer user questions concisely. Use the available builtin tools when helpful.
71+
If a tool call fails, report the error clearly so connectivity issues can be diagnosed.
72+
environment: byoc
73+
tunnel: internal
74+
tools:
75+
builtin: [Bash, Read, Write, Edit, Glob, Grep, WebFetch, WebSearch, DeliverArtifacts]
76+
permissions:
77+
Bash: allow
78+
Read: allow
79+
Write: allow
80+
Edit: allow
81+
Glob: allow
82+
Grep: allow
83+
WebFetch: allow
84+
WebSearch: allow
85+
DeliverArtifacts: allow
86+
delivery:
87+
qoder:
88+
type: forward
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Qoder credential-based Channel
2+
3+
This example declares a Forward Identity, Forward Template, and DingTalk Channel without copying remote Qoder ids.
4+
5+
Set `QODER_PAT`, `SUPPORT_USER_ID`, `DINGTALK_CLIENT_ID`, and `DINGTALK_CLIENT_SECRET`, then run:
6+
7+
```bash
8+
agents plan
9+
agents apply
10+
```
11+
12+
For Feishu, set `type: feishu` and use `app_id` / `app_secret`. For WeCom, set `type: wecom` and use `bot_id` / `secret`. Personal WeChat requires QR binding and is outside this credential-based workflow.

0 commit comments

Comments
 (0)