@eduwork/dsh-oidc 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.en.md +6 -38
  2. package/CHANGELOG.md +7 -39
  3. package/CODE_OF_CONDUCT.en.md +1 -1
  4. package/CODE_OF_CONDUCT.md +1 -1
  5. package/CONTRIBUTING.en.md +1 -1
  6. package/CONTRIBUTING.md +1 -1
  7. package/README.md +19 -16
  8. package/{README.en.md → README_EN.md} +15 -8
  9. package/SECURITY.en.md +1 -1
  10. package/SECURITY.md +1 -1
  11. package/SUPPORT.en.md +1 -1
  12. package/SUPPORT.md +1 -1
  13. package/docs/EDUWORK-MIGRATION.en.md +2 -8
  14. package/docs/EDUWORK-MIGRATION.md +2 -8
  15. package/docs/compatibility.en.md +3 -25
  16. package/docs/compatibility.md +3 -25
  17. package/docs/desktop-host.en.md +55 -55
  18. package/docs/desktop-host.md +64 -64
  19. package/docs/development.en.md +6 -4
  20. package/docs/development.md +6 -4
  21. package/docs/dsh-integration.en.md +2 -2
  22. package/docs/dsh-integration.md +2 -2
  23. package/docs/getting-started.en.md +4 -4
  24. package/docs/getting-started.md +4 -4
  25. package/docs/open-integration.en.md +73 -0
  26. package/docs/open-integration.md +73 -0
  27. package/docs/reasoning-replay.en.md +13 -0
  28. package/docs/reasoning-replay.md +17 -0
  29. package/docs/release-checklist.en.md +7 -61
  30. package/docs/release-checklist.md +7 -61
  31. package/docs/server-integration-contract.en.md +4 -2
  32. package/docs/server-integration-contract.md +5 -3
  33. package/examples/enterprise-profile.example.json +78 -78
  34. package/examples/identity-only.example.json +1 -1
  35. package/examples/resources-discovery.example.json +1 -1
  36. package/lib/provider/core.js +1 -0
  37. package/lib/provider/transform-adapter.js +27 -1
  38. package/lib/provider/transforms.js +0 -1
  39. package/lib/typert.host.js +38 -38
  40. package/lib/typert.remote-client.js +37 -37
  41. package/package.json +6 -5
  42. package/schema/enterprise-profile.v1alpha1.schema.json +193 -193
@@ -1,65 +1,11 @@
1
- # Public release checklist
1
+ # Package release checklist
2
2
 
3
3
  [简体中文](release-checklist.md) | **English**
4
4
 
5
- Record the maintainer, date, command-output links, and exceptions in the release pull request or release record. An applicable but incomplete security, compatibility, or supply-chain item blocks public release; the current maintainer records why an item is not applicable. During the initial single-maintainer phase, do not invent a second human approval: independent technical review, CI/focused regression evidence, and an explicit maintainer decision form the release basis.
5
+ Source and publication are maintained in EduWork. Follow [the shared package workflow](https://github.com/ecnu/EduWork/blob/main/docs/PACKAGES_EN.md); this package retains its independent npm name and SemVer.
6
6
 
7
- This independent stable version is `0.2.0`; the manifest is updated while dev.7 runtime sources and historical frozen artifacts remain unchanged. The repository locks registry-published DSH RC.1 dependencies. npm authorization does not authorize a GitHub push or Release; record actual publication outcomes separately. Local Windows checks must not be reported as completed Linux/Node matrix or institution deployment acceptance.
8
-
9
- ## Authority and project metadata
10
-
11
- - [ ] Repository owner confirmed public release and the MIT license.
12
- - [ ] The `freedomkk-qfeng/dsh-oidc` remote and ownership of the `@eduwork/dsh-oidc` npm package are confirmed.
13
- - [ ] Maintainer, security, and release-manager rosters recorded, accurately stating whether backups and a recovery path currently exist.
14
- - [ ] GitHub Private Vulnerability Reporting is enabled and its entry point has been checked without sensitive data.
15
- - [ ] ECNU/ChatECNU name, example text, colors, and trademark notice approved.
16
-
17
- ## Source and protocol review
18
-
19
- - [ ] Independent technical review completed for OIDC, Key Binding, model-gateway invariants, Profile validation, Provider credentials, and the native boundary; the maintainer accepted findings and exceptions in the release record.
20
- - [ ] Enterprise Profile JSON Schema matches runtime behavior.
21
- - [ ] OpenAPI matches normative Key Binding prose and implementation.
22
- - [ ] OIDC standard references and limitations are current.
23
- - [ ] No unnecessary local reimplementation of a supported DSH capability.
24
- - [ ] Compatibility matrix and changelog updated.
25
-
26
- ## Security and privacy
27
-
28
- - [ ] Threat model reviewed for actual topology.
29
- - [ ] Single-user Credential Provider, Profile ownership and shared-key-slot boundaries verified.
30
- - [ ] OIDC success and negative conformance suite passed against staging.
31
- - [ ] Cross-user/tenant Key Binding authorization tests passed.
32
- - [ ] API-key expiry, rotation, suspension, and remote revocation behavior tested.
33
- - [ ] Confirmed the WebServer listens only on `127.0.0.1` and no reverse proxy, port forward, or tunnel exposes the local session to other users.
34
- - [ ] Remote TLS, CSP, cookies, CSRF, egress, clock, logging, APM, and backups reviewed.
35
- - [ ] Privacy/legal review completed for identity and audit data.
36
- - [ ] Secret scans of both the current tree and publishable Git history found no production hosts, IDs, keys, logs, personal paths, or personal data.
37
-
38
- ## Supply chain
39
-
40
- - [ ] Clean `npm ci` and `npm run check` completed on Windows and Linux with Node 22 and 24.
41
- - [ ] `npm audit` and static analysis reviewed (not merely run).
42
- - [ ] Every direct/transitive license and exception reviewed.
43
- - [ ] npm install scripts reviewed; unexpected scripts denied.
44
- - [ ] Lockfile diff reviewed and dependency versions pinned as intended.
45
- - [ ] GitHub Actions pinned to immutable commits before public release.
46
-
47
- ## Product acceptance
48
-
49
- - [ ] Plain Web DSH: login, callback, display name/sub fallback, provision, model call, refresh, check, logout.
50
- - [ ] Desktop backend: shared Wails/Electron flow, cancellation and model defaults verified; validate any maintained legacy native bridge separately.
51
- - [ ] Native multimodal and text-only + image-transform routes tested.
52
- - [ ] Unsupported reasoning-effort model still runs with thinking enabled.
53
- - [ ] Failure UX tested: Discovery, token, UserInfo, bootstrap, policy denial, model key, expiry, network timeout.
54
-
55
- ## Build and publish
56
-
57
- - [ ] `npm run check` passes from a clean checkout.
58
- - [ ] `npm pack --dry-run` and extracted tarball reviewed; only intended files included.
59
- - [ ] Source maps contain no private absolute paths or secrets.
60
- - [ ] Tag/release notes/checksums prepared.
61
- - [ ] The actual publication path is recorded: until a Trusted Publisher is configured, run the manual workflow only with `publish=false`; the first stable release may use the maintainer's npm CLI plus browser 2FA to publish the frozen tarball, explicitly without claiming provenance.
62
- - [ ] When using Trusted Publishing, the Publisher is bound to repository `freedomkk-qfeng/dsh-oidc`, workflow `release.yml`, and environment `npm`; the job uses pinned npm CLI `11.6.2`, and this commit's Windows/Linux × Node 22/24 CI is successful.
63
- - [ ] Inputs and artifacts are verified for the selected path: the CLI path checks frozen tarball identity, SHA-256, 2FA, and registry results; the Trusted path additionally checks the existing `v<package version>` tag, commit, workflow artifact, and an actual npm provenance attestation.
64
- - [ ] Publishing credentials were not written to the repository, logs, or deliverables, and the release does not claim Trusted Publishing before it is configured.
65
- - [ ] Rollback/deprecation and vulnerability-notification plan confirmed.
7
+ - Review the changed behavior, version, changelog, compatibility and package lock.
8
+ - Run npm ci and npm run check in this package directory, plus relevant real Host or browser acceptance when behavior changes. Use synthetic test data.
9
+ - Inspect the actual tarball, public documentation, licenses and exported entries. Never ship credentials or user data.
10
+ - Select this package in packages-release.yml; publish=false only produces artifacts. Explicit publication runs from the matching package version tag, using the npm environment and this repository's trusted publisher.
11
+ - Verify registry bytes before updating a product lock. Publishing npm does not publish a desktop Release or deploy a client.
@@ -1,65 +1,11 @@
1
- # 公开发布检查表
1
+ # 包发布检查表
2
2
 
3
3
  **简体中文** | [English](release-checklist.en.md)
4
4
 
5
- 在发布 Pull Request 或发行记录中记录维护者、日期、命令输出链接和例外说明。适用但未完成的安全、兼容或供应链项目会阻止公开发布;不适用项由当前维护者记录原因。项目处于初期单一维护者阶段时,不虚构第二位人工审批,以独立技术复核、CI/专项回归证据和维护者明确决定作为发布依据。
5
+ 源码与发布入口统一在 EduWork;遵循[公共包发布流程](https://github.com/ecnu/EduWork/blob/main/docs/PACKAGES.md),本包保留独立的 npm 名称和 SemVer。
6
6
 
7
- 本次独立稳定版本为 `0.2.0`;manifest 已更新,dev.7 的运行源码与历史冻结产物不变。仓库锁使用 npm 可安装的 DSH RC.1 基线。npm 发布授权不等于 GitHub 推送或 Release 授权,按实际执行结果记录。本机 Windows 检查不能冒充 Linux/Node 矩阵或机构真实部署验收。
8
-
9
- ## 授权与项目元数据
10
-
11
- - [ ] 仓库所有者确认公开发布和 MIT 许可证。
12
- - [ ] `freedomkk-qfeng/dsh-oidc` 远程仓库位置和 `@eduwork/dsh-oidc` npm 包所有权均已确认。
13
- - [ ] 维护者、安全维护者和发布经理名单已记录,并如实说明当前是否存在替补及恢复路径。
14
- - [ ] GitHub Private Vulnerability Reporting 已启用并完成一次无敏感数据的入口检查。
15
- - [ ] ECNU/ChatECNU 名称、示例文字、颜色和商标声明获批。
16
-
17
- ## 源码与协议审查
18
-
19
- - [ ] OIDC、Key Binding、模型网关一致性、Profile 校验、Provider 凭据和 native 边界已完成独立技术复核,维护者已在发布记录中接受结论和例外。
20
- - [ ] Enterprise Profile JSON Schema 与运行行为一致。
21
- - [ ] OpenAPI 与 Key Binding 规范正文和实现一致。
22
- - [ ] OIDC 标准引用和限制仍然有效。
23
- - [ ] 没有重复实现 DSH 已支持且适用的能力。
24
- - [ ] 兼容性矩阵和变更日志已更新。
25
-
26
- ## 安全与隐私
27
-
28
- - [ ] 已按实际部署拓扑审查威胁模型。
29
- - [ ] 单机 Credential Provider、Profile 归属与共享 Key 槽隔离边界已验证。
30
- - [ ] 已在预发布环境通过 OIDC 成功和反向符合性测试。
31
- - [ ] 已通过跨用户/跨 tenant Key Binding 授权测试。
32
- - [ ] 已测试 API Key 过期、轮换、停用和远程撤销行为。
33
- - [ ] 已确认 WebServer 仅监听 `127.0.0.1`,没有反向代理、端口转发或隧道把本地会话暴露给其他用户。
34
- - [ ] 已审查远端 TLS、CSP、Cookie、CSRF、出口、时钟、日志、APM 和备份。
35
- - [ ] 已完成身份和审计数据的隐私/法律审查。
36
- - [ ] 当前树与待公开 Git 历史的密钥扫描均未发现生产 host、ID、Key、日志、个人路径或个人数据。
37
-
38
- ## 供应链
39
-
40
- - [ ] Windows 和 Linux 上分别使用 Node 22、24 完成干净 `npm ci` 与 `npm run check`。
41
- - [ ] `npm audit` 和静态分析结果经过审查,而不只是执行。
42
- - [ ] 所有直接/间接许可证和例外已经审查。
43
- - [ ] npm 安装脚本已经审查,未预期脚本被拒绝。
44
- - [ ] Lockfile diff 已审查,依赖版本按预期锁定。
45
- - [ ] 公开发布前,GitHub Actions 已固定到不可变 commit。
46
-
47
- ## 产品验收
48
-
49
- - [ ] 纯 Web DSH:登录、callback、显示 name/sub 降级、provision、模型调用、刷新、检查连接和退出。
50
- - [ ] 桌面 backend:Wails/Electron 共用流程、取消和默认模型正确;仍需维护的旧 native 桥单独验证。
51
- - [ ] 原生多模态和纯文本 + image-transform 路由均已测试。
52
- - [ ] 不支持 reasoning effort 的模型仍能启用 thinking 并正常运行。
53
- - [ ] 已测试失败 UX:Discovery、token、UserInfo、bootstrap、策略拒绝、模型 Key、过期和网络超时。
54
-
55
- ## 构建与发布
56
-
57
- - [ ] 在干净 checkout 中通过 `npm run check`。
58
- - [ ] 已审查 `npm pack --dry-run` 和解压后的 tarball,且只包含预期文件。
59
- - [ ] Source map 不包含私有绝对路径或秘密。
60
- - [ ] Tag、发布说明和 checksum 已准备。
61
- - [ ] 实际发布路径已记录:Trusted Publisher 尚未配置时,手动 workflow 只以 `publish=false` 运行;首次稳定版可以使用维护者 npm CLI + 浏览器 2FA 发布冻结 tarball,且明确不声称 provenance。
62
- - [ ] 若使用 Trusted Publishing,Publisher 已绑定仓库 `freedomkk-qfeng/dsh-oidc`、workflow `release.yml` 和 environment `npm`;发布 job 使用固定 npm CLI `11.6.2`,且该提交的 Windows/Linux × Node 22/24 CI 已成功。
63
- - [ ] 发布输入与产物已按所选路径核验:CLI 路径核对冻结 tarball 身份、SHA-256、2FA 和 registry 结果;Trusted 路径还核对现有 `v<package version>` tag、提交、workflow artifact 和实际 npm provenance attestation。
64
- - [ ] 发布凭据没有写入仓库、日志或交付物;未配置 Trusted Publisher 时也不宣称使用了 Trusted Publishing。
65
- - [ ] 已确认回滚、弃用和漏洞通知计划。
7
+ - 核对变更行为、版本、变更记录、兼容范围和依赖锁。
8
+ - 在当前包目录执行 npm ci、npm run check;有行为变化时补对应 Host 或浏览器实测,使用合成测试数据。
9
+ - 检查实际 tarball、公开文档、许可证和导出入口,不携带凭据或用户数据。
10
+ - 在 packages-release.yml 中选择本包;publish=false 仅产出候选。明确发布时从匹配包版本的 tag 运行,使用 npm environment 与本仓库的 Trusted Publisher。
11
+ - 核验 registry 中的实际字节后再更新产品锁。发布 npm 不会发布桌面 Release 或替换客户端。
@@ -1,13 +1,15 @@
1
1
  # EduWork identity and resource integration (RFC EW-IDENTITY-1)
2
2
 
3
- > Scope update in .20260910.6: quota and activity heartbeat are institution-only extensions. The public package never requests /quota and its resources RPC contains only model metadata. No capability string or configuration flag enables quota in the public package. ECNU retains its existing server wire in a separate package.
3
+ > Capability scope: quota and activity heartbeat are institution-only extensions. The public package never requests /quota and its resources RPC contains only model metadata. No capability string or configuration flag enables quota in the public package. ECNU retains its existing server wire in a separate package.
4
4
 
5
- > Current candidate supports two modes: identity-only OIDC can omit all resource interfaces and use personal API keys. The complete organization-resource mode below adds managed credentials and models; see [Public resource protocol](public-resource-protocol.en.md). The mandatory requirements below apply to complete integration, not identity-only login.
5
+ > Two integration modes are supported: identity-only OIDC can omit all resource interfaces and use personal API keys. The complete organization-resource mode below adds managed credentials and models; see [Public resource protocol](public-resource-protocol.en.md). The mandatory requirements below apply to complete integration, not identity-only login.
6
6
 
7
7
  [简体中文](server-integration-contract.md) | **English**
8
8
 
9
9
  This document is written for identity-platform, model-platform, and integration-test teams. It defines, in API-reference form, the server capabilities required by `dsh-oidc`.
10
10
 
11
+ For the goals behind these interfaces and ways to contribute to their evolution, see the [Open Identity and Model Integration Initiative](open-integration.en.md). This specification remains the implementation and integration reference; the initiative does not change existing fields, paths, or compatibility rules.
12
+
11
13
  Status: implemented client wire specification, not an IETF standard. Source code and HTTP contract tests determine current behavior. Section 10 provides curl examples; section 11 contains **unimplemented optional proposals**. Every client is a local, single-user Runtime; this is not a shared Web-login architecture.
12
14
 
13
15
  | Capability | Required interfaces | Optionality and implemented behavior |
@@ -1,13 +1,15 @@
1
1
  # EduWork 身份与资源接入规范(RFC EW-IDENTITY-1)
2
2
 
3
- > .20260910.6 范围调整:配额与活跃心跳属于机构专属扩展。公版不请求 /quota,resources RPC 只返回模型元数据;任何 capabilities 字符串或公版配置开关都不会启用配额。ECNU 在独立机构包中沿用原配额服务协议。
3
+ > 能力范围:配额与活跃心跳属于机构专属扩展。公版不请求 /quota,resources RPC 只返回模型元数据;任何 capabilities 字符串或公版配置开关都不会启用配额。ECNU 在独立机构包中沿用原配额服务协议。
4
4
 
5
- > 当前候选支持两种模式:纯身份 OIDC 可省略全部资源接口,用户自配个人 Key;下文完整机构资源模式提供托管凭据与模型,见 [公共资源协议](public-resource-protocol.md)。下文的完整交付要求不适用于只做身份接入的场景。
5
+ > 支持两种接入模式:纯身份 OIDC 可省略全部资源接口,用户自配个人 Key;下文完整机构资源模式提供托管凭据与模型,见 [公共资源协议](public-resource-protocol.md)。下文的完整交付要求不适用于只做身份接入的场景。
6
6
 
7
7
  **简体中文** | [English](server-integration-contract.en.md)
8
8
 
9
9
  本文面向身份平台、模型平台和联调测试人员,以接口说明的形式定义接入 `dsh-oidc` 时服务端必须提供的能力。
10
10
 
11
+ 希望了解这套接口的目标或参与后续演进,请先看[开放身份与模型接入倡议](open-integration.md)。本规范是实现与联调依据,倡议不改变现有字段、路径或兼容性约定。
12
+
11
13
  状态:现有客户端 wire 规范;不是 IETF 标准。本文以 `@eduwork/dsh-oidc` 源码及 HTTP 契约测试为准。第 10 节列出需填入本次授权结果的 curl 请求示例;第 11 节是**尚未实现的可选扩展提案**。所有客户端都是用户本机的单用户 Runtime,不设计共享 Web 服务端登录会话。
12
14
 
13
15
  | 能力档 | 最小交付 | 可选性与当前行为 |
@@ -816,7 +818,7 @@ curl --fail-with-body --max-time 10 "$REVOCATION_ENDPOINT" \
816
818
 
817
819
  ### 11.1 机构资源发现
818
820
 
819
- 当前公版只读取本机可信 JSONC/Profile;**不会请求** `/.well-known/eduwork-configuration`。后续可约定此 HTTPS 入口返回纯数据 `protocol_version: eduwork-discovery/v1alpha1`,包含机构展示、OIDC Public Client、资源基址和能力名称。必须由用户/发行方明确导入,核对 issuer/provider/origin 后转换成本机配置;不得自动更换凭据目标,不得下发插件、脚本、CSS、可执行命令或任意模型 adapter。具体字段、签名/信任根、版本协商另行评审,不能把该提案当作本轮服务器必做接口。
821
+ 当前公版只读取本机可信 JSONC/Profile;**不会请求** `/.well-known/eduwork-configuration`。后续可约定此 HTTPS 入口返回纯数据 `protocol_version: eduwork-discovery/v1alpha1`,包含机构展示、OIDC Public Client、资源基址和能力名称。必须由用户/发行方明确导入,核对 issuer/provider/origin 后转换成本机配置;不得自动更换凭据目标,不得下发插件、脚本、CSS、可执行命令或任意模型 adapter。具体字段、签名/信任根、版本协商另行评审,不能把该提案当作当前协议的必需接口。
820
822
 
821
823
  ### 11.2 机构活跃心跳
822
824
 
@@ -1,78 +1,78 @@
1
- {
2
- "schemaVersion": "dsh-oidc/v1alpha1",
3
- "id": "example-university",
4
- "displayName": "Example University AI",
5
- "organization": "Example University",
6
- "brand": {
7
- "productName": "Example AI Work",
8
- "organizationName": "Example University",
9
- "mark": "E",
10
- "primaryColor": "#5157af",
11
- "loginTitle": "Sign in with Example University",
12
- "loginDescription": "Authenticate with the university identity provider and bind a model runtime credential. Your password never enters DSH.",
13
- "supportURL": "https://support.example.edu/ai"
14
- },
15
- "oidc": {
16
- "issuer": "https://id.example.edu/oidc",
17
- "clientId": "dsh-web-public-client",
18
- "scopes": [
19
- "openid",
20
- "profile",
21
- "offline_access",
22
- "worker.bootstrap.read",
23
- "worker.credential.provision",
24
- "worker.credential.read",
25
- "worker.credential.renew"
26
- ]
27
- },
28
- "keyBinding": {
29
- "baseURL": "https://ai.example.edu/api/worker/v1",
30
- "credentialRef": "EDUWORK_API_KEY"
31
- },
32
- "provider": {
33
- "id": "example-ai",
34
- "displayName": "Example AI",
35
- "adapter": "openai-compatible",
36
- "baseURL": "https://ai.example.edu/open/api/v1",
37
- "reasoning": "high",
38
- "defaultContextWindow": 262144,
39
- "defaultMaxTokens": 32768,
40
- "streamIdleTimeoutMs": 300000,
41
- "retryPolicy": {
42
- "mode": "normal",
43
- "maxRetries": 2
44
- },
45
- "compat": {
46
- "supportsDeveloperRole": false,
47
- "supportsStore": false,
48
- "supportsReasoningEffort": true,
49
- "supportsUsageInStreaming": true,
50
- "maxTokensField": "max_tokens",
51
- "supportsStrictMode": false,
52
- "supportsLongCacheRetention": false,
53
- "thinkingFormat": "deepseek",
54
- "requiresReasoningContentOnAssistantMessages": true
55
- },
56
- "models": [
57
- {
58
- "id": "example-max",
59
- "name": "Example Max",
60
- "input": ["text"],
61
- "reasoningEfforts": {
62
- "max": "max",
63
- "high": "high",
64
- "low": "low"
65
- },
66
- "defaultReasoningEffort": "high"
67
- },
68
- {
69
- "id": "example-plus",
70
- "name": "Example Plus",
71
- "input": ["text", "image"],
72
- "compat": {
73
- "supportsReasoningEffort": false
74
- }
75
- }
76
- ]
77
- }
78
- }
1
+ {
2
+ "schemaVersion": "dsh-oidc/v1alpha1",
3
+ "id": "example-university",
4
+ "displayName": "Example University AI",
5
+ "organization": "Example University",
6
+ "brand": {
7
+ "productName": "Example AI Work",
8
+ "organizationName": "Example University",
9
+ "mark": "E",
10
+ "primaryColor": "#5157af",
11
+ "loginTitle": "Sign in with Example University",
12
+ "loginDescription": "Authenticate with the university identity provider and bind a model runtime credential. Your password never enters DSH.",
13
+ "supportURL": "https://support.example.edu/ai"
14
+ },
15
+ "oidc": {
16
+ "issuer": "https://id.example.edu/oidc",
17
+ "clientId": "replace-with-public-client-id",
18
+ "scopes": [
19
+ "openid",
20
+ "profile",
21
+ "offline_access",
22
+ "worker.bootstrap.read",
23
+ "worker.credential.provision",
24
+ "worker.credential.read",
25
+ "worker.credential.renew"
26
+ ]
27
+ },
28
+ "keyBinding": {
29
+ "baseURL": "https://ai.example.edu/api/worker/v1",
30
+ "credentialRef": "EDUWORK_API_KEY"
31
+ },
32
+ "provider": {
33
+ "id": "example-ai",
34
+ "displayName": "Example AI",
35
+ "adapter": "openai-compatible",
36
+ "baseURL": "https://ai.example.edu/open/api/v1",
37
+ "reasoning": "high",
38
+ "defaultContextWindow": 262144,
39
+ "defaultMaxTokens": 32768,
40
+ "streamIdleTimeoutMs": 300000,
41
+ "retryPolicy": {
42
+ "mode": "normal",
43
+ "maxRetries": 2
44
+ },
45
+ "compat": {
46
+ "supportsDeveloperRole": false,
47
+ "supportsStore": false,
48
+ "supportsReasoningEffort": true,
49
+ "supportsUsageInStreaming": true,
50
+ "maxTokensField": "max_tokens",
51
+ "supportsStrictMode": false,
52
+ "supportsLongCacheRetention": false,
53
+ "thinkingFormat": "deepseek",
54
+ "requiresReasoningContentOnAssistantMessages": true
55
+ },
56
+ "models": [
57
+ {
58
+ "id": "example-max",
59
+ "name": "Example Max",
60
+ "input": ["text"],
61
+ "reasoningEfforts": {
62
+ "max": "max",
63
+ "high": "high",
64
+ "low": "low"
65
+ },
66
+ "defaultReasoningEffort": "high"
67
+ },
68
+ {
69
+ "id": "example-plus",
70
+ "name": "Example Plus",
71
+ "input": ["text", "image"],
72
+ "compat": {
73
+ "supportsReasoningEffort": false
74
+ }
75
+ }
76
+ ]
77
+ }
78
+ }
@@ -4,7 +4,7 @@
4
4
  "displayName": "Example Organization",
5
5
  "oidc": {
6
6
  "issuer": "https://identity.example.edu",
7
- "clientId": "example-public-client",
7
+ "clientId": "replace-with-public-client-id",
8
8
  "scopes": ["openid", "profile", "offline_access"]
9
9
  }
10
10
  }
@@ -4,7 +4,7 @@
4
4
  "displayName": "Example Organization",
5
5
  "oidc": {
6
6
  "issuer": "https://identity.example.edu",
7
- "clientId": "example-public-client",
7
+ "clientId": "replace-with-public-client-id",
8
8
  "scopes": ["openid", "profile", "offline_access"]
9
9
  },
10
10
  "keyBinding": {
@@ -161,6 +161,7 @@ export function resolveEnterpriseProfiles(raw = {}) {
161
161
  compat,
162
162
  modelPolicy: {
163
163
  reasoning, supportsReasoningEffort, reasoningEfforts: efforts, defaultReasoningEffort,
164
+ requiresReasoningContent: reasoning && compat.requiresReasoningContentOnAssistantMessages === true,
164
165
  ...(internalThinkingEffort === undefined ? {} : { internalThinkingEffort }),
165
166
  },
166
167
  }
@@ -1,5 +1,31 @@
1
1
  import { LlmError } from '@deepseek-ai/dsh-llm'
2
2
 
3
+ /** pi-ai preserves the response's raw reasoning alias in replay metadata.
4
+ * Routes requiring reasoning_content must replay that same text under the
5
+ * canonical field, otherwise pi-ai adds an empty alias and strict gateways
6
+ * reject the pair. Change only the outgoing copy; durable history is untouched.
7
+ */
8
+ export function canonicalReasoningReplay(request, policy) {
9
+ if (!policy.requiresReasoningContent) return request
10
+ let changed = false
11
+ const messages = request.messages.map(message => {
12
+ const source = message.source, replay = source?.replayState
13
+ if (message.role !== 'assistant' || source?.kind !== 'model' || source.provider !== request.provider || source.model !== request.model
14
+ || replay?.response?.kind !== 'pi-ai' || replay.response.version !== 2 || replay.response.api !== 'openai-completions'
15
+ || !Array.isArray(replay.blocks)) return message
16
+ let replaced = false
17
+ const blocks = replay.blocks.map(block => {
18
+ if (block.type !== 'reasoning' || !['reasoning', 'reasoning_text'].includes(block.thinkingSignature)) return block
19
+ replaced = true
20
+ return { ...block, thinkingSignature: 'reasoning_content' }
21
+ })
22
+ if (!replaced) return message
23
+ changed = true
24
+ return { ...message, source: { ...source, replayState: { ...replay, blocks } } }
25
+ })
26
+ return changed ? { ...request, messages } : request
27
+ }
28
+
3
29
  /** Delegate one enterprise route while applying capabilities contributed by replaceable feature plugins. */
4
30
  export class TransformingEnterpriseAdapter {
5
31
  constructor(inner, transforms, modelPolicy = () => ({})) {
@@ -73,7 +99,7 @@ export class TransformingEnterpriseAdapter {
73
99
  }
74
100
  request = { ...options, reasoningEffort: effort }
75
101
  }
76
- yield* prepared.stream(await this.transforms.apply(request, nativeModelInfo))
102
+ yield* prepared.stream(canonicalReasoningReplay(await this.transforms.apply(request, nativeModelInfo), policy))
77
103
  }
78
104
 
79
105
  async * stream(options) {
@@ -56,4 +56,3 @@ export class EnterpriseModelTransforms extends Service {
56
56
  return transformed
57
57
  }
58
58
  }
59
-
@@ -1,44 +1,44 @@
1
- import {
1
+ import {
2
2
  accountResult, baseURLSchema, beginResult, configurationResult, configurationTargetSchema, openConfigurationResult, jsonParameter, managementResult, loginIdSchema, loginResult,
3
3
  modelCatalogModeSchema, profileIdSchema, reconcileOptionsSchema, restartResult, runtimeModelsSchema, resourcesResult, enterpriseSelectionResult, enterpriseSelectionOptionsSchema,
4
- } from './typert-schemas.js'
5
-
6
- const pkg = '@eduwork/dsh-oidc'
7
- const source = { file: 'lib/index.js', line: 1, column: 1 }
8
- const profile = () => jsonParameter('profileID', profileIdSchema, `${pkg}#ProfileID`)
9
- const login = () => jsonParameter('loginID', loginIdSchema, `${pkg}#LoginID`)
10
- const options = () => jsonParameter('options', reconcileOptionsSchema, `${pkg}#ReconcileOptions`)
11
- const baseURL = () => jsonParameter('baseURL', baseURLSchema, `${pkg}#BaseURL`)
12
- const modelMode = () => jsonParameter('modelMode', modelCatalogModeSchema, `${pkg}#ModelCatalogMode`)
13
- const models = () => jsonParameter('models', runtimeModelsSchema, `${pkg}#RuntimeModels`)
14
- const descriptor = (method, parameters, result) => ({
15
- id: `${pkg}#oidcAccounts/${method}`, service: 'oidcAccounts', namespace: 'oidcAccounts', method,
16
- invocation: { kind: 'direct' }, parameters, result, sourceLocation: source,
17
- })
18
-
19
- export const TYPERT = {
20
- package: pkg, face: 'host', schemas: [],
21
- invocations: [
4
+ } from './typert-schemas.js'
5
+
6
+ const pkg = '@eduwork/dsh-oidc'
7
+ const source = { file: 'lib/index.js', line: 1, column: 1 }
8
+ const profile = () => jsonParameter('profileID', profileIdSchema, `${pkg}#ProfileID`)
9
+ const login = () => jsonParameter('loginID', loginIdSchema, `${pkg}#LoginID`)
10
+ const options = () => jsonParameter('options', reconcileOptionsSchema, `${pkg}#ReconcileOptions`)
11
+ const baseURL = () => jsonParameter('baseURL', baseURLSchema, `${pkg}#BaseURL`)
12
+ const modelMode = () => jsonParameter('modelMode', modelCatalogModeSchema, `${pkg}#ModelCatalogMode`)
13
+ const models = () => jsonParameter('models', runtimeModelsSchema, `${pkg}#RuntimeModels`)
14
+ const descriptor = (method, parameters, result) => ({
15
+ id: `${pkg}#oidcAccounts/${method}`, service: 'oidcAccounts', namespace: 'oidcAccounts', method,
16
+ invocation: { kind: 'direct' }, parameters, result, sourceLocation: source,
17
+ })
18
+
19
+ export const TYPERT = {
20
+ package: pkg, face: 'host', schemas: [],
21
+ invocations: [
22
22
  descriptor('configuration', [], configurationResult),
23
23
  descriptor('openConfiguration', [jsonParameter('target', configurationTargetSchema, `${pkg}#ConfigurationTarget`)], openConfigurationResult),
24
- descriptor('status', [profile()], accountResult),
25
- descriptor('resources', [profile()], resourcesResult),
26
- descriptor('begin', [profile()], beginResult),
27
- descriptor('loginStatus', [login()], loginResult),
28
- descriptor('cancelLogin', [login()], loginResult),
24
+ descriptor('status', [profile()], accountResult),
25
+ descriptor('resources', [profile()], resourcesResult),
26
+ descriptor('begin', [profile()], beginResult),
27
+ descriptor('loginStatus', [login()], loginResult),
28
+ descriptor('cancelLogin', [login()], loginResult),
29
29
  descriptor('reconcile', [profile(), options()], accountResult),
30
30
  descriptor('selectEnterpriseModel', [profile(), jsonParameter('options', enterpriseSelectionOptionsSchema, `${pkg}#EnterpriseSelectionOptions`)], enterpriseSelectionResult),
31
- descriptor('logout', [profile()], accountResult),
32
- descriptor('management', [], managementResult),
33
- descriptor('activate', [profile()], managementResult),
34
- descriptor('configure', [profile()], managementResult),
35
- descriptor('addCustom', [baseURL()], managementResult),
36
- descriptor('updateCustom', [profile(), baseURL()], managementResult),
37
- descriptor('removeProfile', [profile()], managementResult),
38
- descriptor('configureModels', [profile(), modelMode(), models()], managementResult),
39
- descriptor('restart', [], restartResult),
40
- ],
41
- model: { services: [], events: [], objects: [] },
42
- }
43
-
44
- export default TYPERT
31
+ descriptor('logout', [profile()], accountResult),
32
+ descriptor('management', [], managementResult),
33
+ descriptor('activate', [profile()], managementResult),
34
+ descriptor('configure', [profile()], managementResult),
35
+ descriptor('addCustom', [baseURL()], managementResult),
36
+ descriptor('updateCustom', [profile(), baseURL()], managementResult),
37
+ descriptor('removeProfile', [profile()], managementResult),
38
+ descriptor('configureModels', [profile(), modelMode(), models()], managementResult),
39
+ descriptor('restart', [], restartResult),
40
+ ],
41
+ model: { services: [], events: [], objects: [] },
42
+ }
43
+
44
+ export default TYPERT
@@ -1,43 +1,43 @@
1
- import {
1
+ import {
2
2
  accountResult, baseURLSchema, beginResult, configurationResult, configurationTargetSchema, openConfigurationResult, jsonParameter, managementResult, loginIdSchema, loginResult,
3
3
  modelCatalogModeSchema, profileIdSchema, reconcileOptionsSchema, restartResult, runtimeModelsSchema, resourcesResult, enterpriseSelectionResult, enterpriseSelectionOptionsSchema,
4
- } from './typert-schemas.js'
5
-
6
- const pkg = '@eduwork/dsh-oidc'
7
- const source = { file: 'lib/index.js', line: 1, column: 1 }
8
- const profile = () => jsonParameter('profileID', profileIdSchema, `${pkg}#ProfileID`)
9
- const login = () => jsonParameter('loginID', loginIdSchema, `${pkg}#LoginID`)
10
- const options = () => jsonParameter('options', reconcileOptionsSchema, `${pkg}#ReconcileOptions`)
11
- const baseURL = () => jsonParameter('baseURL', baseURLSchema, `${pkg}#BaseURL`)
12
- const modelMode = () => jsonParameter('modelMode', modelCatalogModeSchema, `${pkg}#ModelCatalogMode`)
13
- const models = () => jsonParameter('models', runtimeModelsSchema, `${pkg}#RuntimeModels`)
14
- const descriptor = (method, parameters, result) => ({
15
- id: `${pkg}#oidcAccounts/${method}`, service: 'oidcAccounts', namespace: 'oidcAccounts', method,
16
- invocation: { kind: 'direct' }, parameters, result, sourceLocation: source,
17
- })
18
-
19
- export const TYPERT_REMOTE = {
20
- package: pkg,
21
- descriptors: [
4
+ } from './typert-schemas.js'
5
+
6
+ const pkg = '@eduwork/dsh-oidc'
7
+ const source = { file: 'lib/index.js', line: 1, column: 1 }
8
+ const profile = () => jsonParameter('profileID', profileIdSchema, `${pkg}#ProfileID`)
9
+ const login = () => jsonParameter('loginID', loginIdSchema, `${pkg}#LoginID`)
10
+ const options = () => jsonParameter('options', reconcileOptionsSchema, `${pkg}#ReconcileOptions`)
11
+ const baseURL = () => jsonParameter('baseURL', baseURLSchema, `${pkg}#BaseURL`)
12
+ const modelMode = () => jsonParameter('modelMode', modelCatalogModeSchema, `${pkg}#ModelCatalogMode`)
13
+ const models = () => jsonParameter('models', runtimeModelsSchema, `${pkg}#RuntimeModels`)
14
+ const descriptor = (method, parameters, result) => ({
15
+ id: `${pkg}#oidcAccounts/${method}`, service: 'oidcAccounts', namespace: 'oidcAccounts', method,
16
+ invocation: { kind: 'direct' }, parameters, result, sourceLocation: source,
17
+ })
18
+
19
+ export const TYPERT_REMOTE = {
20
+ package: pkg,
21
+ descriptors: [
22
22
  descriptor('configuration', [], configurationResult),
23
23
  descriptor('openConfiguration', [jsonParameter('target', configurationTargetSchema, `${pkg}#ConfigurationTarget`)], openConfigurationResult),
24
- descriptor('status', [profile()], accountResult),
25
- descriptor('resources', [profile()], resourcesResult),
26
- descriptor('begin', [profile()], beginResult),
27
- descriptor('loginStatus', [login()], loginResult),
28
- descriptor('cancelLogin', [login()], loginResult),
24
+ descriptor('status', [profile()], accountResult),
25
+ descriptor('resources', [profile()], resourcesResult),
26
+ descriptor('begin', [profile()], beginResult),
27
+ descriptor('loginStatus', [login()], loginResult),
28
+ descriptor('cancelLogin', [login()], loginResult),
29
29
  descriptor('reconcile', [profile(), options()], accountResult),
30
30
  descriptor('selectEnterpriseModel', [profile(), jsonParameter('options', enterpriseSelectionOptionsSchema, `${pkg}#EnterpriseSelectionOptions`)], enterpriseSelectionResult),
31
- descriptor('logout', [profile()], accountResult),
32
- descriptor('management', [], managementResult),
33
- descriptor('activate', [profile()], managementResult),
34
- descriptor('configure', [profile()], managementResult),
35
- descriptor('addCustom', [baseURL()], managementResult),
36
- descriptor('updateCustom', [profile(), baseURL()], managementResult),
37
- descriptor('removeProfile', [profile()], managementResult),
38
- descriptor('configureModels', [profile(), modelMode(), models()], managementResult),
39
- descriptor('restart', [], restartResult),
40
- ],
41
- }
42
-
43
- export default TYPERT_REMOTE
31
+ descriptor('logout', [profile()], accountResult),
32
+ descriptor('management', [], managementResult),
33
+ descriptor('activate', [profile()], managementResult),
34
+ descriptor('configure', [profile()], managementResult),
35
+ descriptor('addCustom', [baseURL()], managementResult),
36
+ descriptor('updateCustom', [profile(), baseURL()], managementResult),
37
+ descriptor('removeProfile', [profile()], managementResult),
38
+ descriptor('configureModels', [profile(), modelMode(), models()], managementResult),
39
+ descriptor('restart', [], restartResult),
40
+ ],
41
+ }
42
+
43
+ export default TYPERT_REMOTE