@eduwork/dsh-oidc 0.1.0-alpha.11
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.
- package/CHANGELOG.en.md +120 -0
- package/CHANGELOG.md +120 -0
- package/LICENSE +21 -0
- package/README.en.md +184 -0
- package/README.md +143 -0
- package/THIRD_PARTY_NOTICES.en.md +51 -0
- package/THIRD_PARTY_NOTICES.md +53 -0
- package/cordis.patch.yml +8 -0
- package/docs/EDUWORK-MIGRATION.en.md +23 -0
- package/docs/EDUWORK-MIGRATION.md +23 -0
- package/docs/architecture.en.md +125 -0
- package/docs/architecture.md +125 -0
- package/docs/compatibility.en.md +61 -0
- package/docs/compatibility.md +61 -0
- package/docs/decisions/0001-one-plugin-composition-boundary.en.md +34 -0
- package/docs/decisions/0001-one-plugin-composition-boundary.md +34 -0
- package/docs/dsh-integration.en.md +171 -0
- package/docs/dsh-integration.md +171 -0
- package/docs/ecnu-reference.en.md +66 -0
- package/docs/ecnu-reference.md +66 -0
- package/docs/enterprise-profile.en.md +159 -0
- package/docs/enterprise-profile.md +159 -0
- package/docs/getting-started.en.md +137 -0
- package/docs/getting-started.md +239 -0
- package/docs/key-binding-protocol.en.md +227 -0
- package/docs/key-binding-protocol.md +227 -0
- package/docs/oidc-interoperability.en.md +100 -0
- package/docs/oidc-interoperability.md +100 -0
- package/docs/release-checklist.en.md +60 -0
- package/docs/release-checklist.md +60 -0
- package/docs/security-model.en.md +138 -0
- package/docs/security-model.md +138 -0
- package/docs/server-integration-contract.en.md +706 -0
- package/docs/server-integration-contract.md +706 -0
- package/examples/ecnu.enterprise-profile.example.json +81 -0
- package/examples/enterprise-profile.example.json +77 -0
- package/lib/client.js +5855 -0
- package/lib/index.js +59 -0
- package/lib/oidc.js +649 -0
- package/lib/profile.js +371 -0
- package/lib/provider/core.js +138 -0
- package/lib/provider/index.js +156 -0
- package/lib/provider/transform-adapter.js +65 -0
- package/lib/provider/transforms.js +59 -0
- package/lib/typert-schemas.js +84 -0
- package/lib/typert.host.js +38 -0
- package/lib/typert.remote-client.js +37 -0
- package/package.json +120 -0
- package/protocol/openapi.yaml +257 -0
- package/schema/enterprise-profile.v1alpha1.schema.json +174 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** Delegate one enterprise route while applying capabilities contributed by replaceable feature plugins. */
|
|
2
|
+
export class TransformingEnterpriseAdapter {
|
|
3
|
+
constructor(inner, transforms, modelPolicy = () => ({})) {
|
|
4
|
+
this.inner = inner
|
|
5
|
+
this.transforms = transforms
|
|
6
|
+
this.modelPolicy = modelPolicy
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
providerInfo(provider) { return this.inner.providerInfo(provider) }
|
|
10
|
+
providerRetryPolicy(provider) { return this.inner.providerRetryPolicy(provider) }
|
|
11
|
+
|
|
12
|
+
async listModels(provider) {
|
|
13
|
+
const models = await this.inner.listModels(provider)
|
|
14
|
+
return models.map(model => ({
|
|
15
|
+
...model,
|
|
16
|
+
inputModalities: this.transforms.capabilities(provider, model.id, model.inputModalities),
|
|
17
|
+
}))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async resolveModel(provider, model, signal) {
|
|
21
|
+
return this.decorateModel(provider, model, await this.inner.resolveModel(provider, model, signal))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
decorateModel(provider, model, resolved) {
|
|
25
|
+
const decorated = {
|
|
26
|
+
...resolved,
|
|
27
|
+
inputModalities: this.transforms.capabilities(provider, model, resolved.inputModalities),
|
|
28
|
+
}
|
|
29
|
+
const policy = this.modelPolicy(provider, model)
|
|
30
|
+
if (policy.reasoning === false || policy.supportsReasoningEffort !== false) return decorated
|
|
31
|
+
const { reasoning: _unsupportedReasoningEfforts, ...thinkingOnly } = decorated
|
|
32
|
+
return thinkingOnly
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async prepareCall(provider, model, signal) {
|
|
36
|
+
const prepared = typeof this.inner.prepareCall === 'function'
|
|
37
|
+
? await this.inner.prepareCall(provider, model, signal)
|
|
38
|
+
: { model: await this.inner.resolveModel(provider, model, signal), stream: options => this.inner.stream(options) }
|
|
39
|
+
return {
|
|
40
|
+
model: this.decorateModel(provider, model, prepared.model),
|
|
41
|
+
stream: options => this.streamPrepared(options, prepared),
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async * streamPrepared(options, prepared) {
|
|
46
|
+
const nativeModelInfo = prepared.model
|
|
47
|
+
let request = options
|
|
48
|
+
const policy = this.modelPolicy(options.provider, options.model)
|
|
49
|
+
if (policy.reasoning === false) {
|
|
50
|
+
request = { ...options, reasoningEffort: 'off' }
|
|
51
|
+
} else if (policy.supportsReasoningEffort === false) {
|
|
52
|
+
const { reasoningEffort: _unsupportedReasoningEffort, ...withoutEffort } = options
|
|
53
|
+
request = withoutEffort
|
|
54
|
+
}
|
|
55
|
+
yield* prepared.stream(await this.transforms.apply(request, nativeModelInfo))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async * stream(options) {
|
|
59
|
+
const prepared = typeof this.inner.prepareCall === 'function'
|
|
60
|
+
? await this.inner.prepareCall(options.provider, options.model, options.signal)
|
|
61
|
+
: { model: await this.inner.resolveModel(options.provider, options.model, options.signal), stream: request => this.inner.stream(request) }
|
|
62
|
+
yield* this.streamPrepared(options, prepared)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Service } from '@deepseek-ai/cordis'
|
|
2
|
+
|
|
3
|
+
function routeKey(provider, model) {
|
|
4
|
+
return `${provider}\u0000${model ?? '*'}`
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function normalizedModalities(inputModalities) {
|
|
8
|
+
return [...new Set(inputModalities ?? [])]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Stable extension seam for plugins that add a capability to an enterprise
|
|
13
|
+
* route without replacing the route owner or mutating the durable transcript.
|
|
14
|
+
*/
|
|
15
|
+
export class EnterpriseModelTransforms extends Service {
|
|
16
|
+
constructor(ctx) {
|
|
17
|
+
super(ctx, 'enterpriseTransforms')
|
|
18
|
+
this.entries = new Map()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
register({ provider, model, inputModalities = [], when = () => true, transform }) {
|
|
22
|
+
if (typeof provider !== 'string' || provider.length === 0) throw new Error('enterprise transform provider is required')
|
|
23
|
+
if (model !== undefined && (typeof model !== 'string' || model.length === 0)) throw new Error('enterprise transform model must be a non-empty string when provided')
|
|
24
|
+
if (typeof when !== 'function') throw new Error('enterprise transform condition must be a function')
|
|
25
|
+
if (typeof transform !== 'function') throw new Error('enterprise transform must be a function')
|
|
26
|
+
const key = routeKey(provider, model)
|
|
27
|
+
if (this.entries.has(key)) throw new Error(`enterprise transform already registered for ${provider}/${model ?? '*'}`)
|
|
28
|
+
const entry = Object.freeze({ provider, model, inputModalities: normalizedModalities(inputModalities), when, transform })
|
|
29
|
+
this.entries.set(key, entry)
|
|
30
|
+
this.ctx.emit('llm/adapters-updated')
|
|
31
|
+
return () => {
|
|
32
|
+
if (this.entries.get(key) !== entry) return
|
|
33
|
+
this.entries.delete(key)
|
|
34
|
+
this.ctx.emit('llm/adapters-updated')
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
matching(provider, model, nativeInputModalities) {
|
|
39
|
+
const native = normalizedModalities(nativeInputModalities)
|
|
40
|
+
return [
|
|
41
|
+
this.entries.get(routeKey(provider, undefined)),
|
|
42
|
+
this.entries.get(routeKey(provider, model)),
|
|
43
|
+
].filter(entry => entry !== undefined && entry.when({ provider, model, inputModalities: native }))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
capabilities(provider, model, inputModalities) {
|
|
47
|
+
const extra = this.matching(provider, model, inputModalities).flatMap(entry => entry.inputModalities)
|
|
48
|
+
return normalizedModalities([...(inputModalities ?? []), ...extra])
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async apply(options, nativeModelInfo) {
|
|
52
|
+
let transformed = options
|
|
53
|
+
for (const entry of this.matching(options.provider, options.model, nativeModelInfo?.inputModalities)) {
|
|
54
|
+
transformed = await entry.transform(transformed, nativeModelInfo)
|
|
55
|
+
}
|
|
56
|
+
return transformed
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
const pkg = '@eduwork/dsh-oidc'
|
|
4
|
+
export const profileIdSchema = z.string().min(1).max(64)
|
|
5
|
+
export const reconcileOptionsSchema = z.object({ allowProvision: z.boolean().optional() }).strict()
|
|
6
|
+
export const baseURLSchema = z.string().min(1).max(2048)
|
|
7
|
+
export const modelCatalogModeSchema = z.enum(['discovery', 'manual'])
|
|
8
|
+
|
|
9
|
+
const brandSchema = z.object({
|
|
10
|
+
productName: z.string().optional(), organizationName: z.string().optional(), mark: z.string().optional(),
|
|
11
|
+
logoURL: z.string().optional(), primaryColor: z.string().optional(), loginTitle: z.string().optional(),
|
|
12
|
+
loginDescription: z.string().optional(), supportURL: z.string().optional(),
|
|
13
|
+
}).strict()
|
|
14
|
+
|
|
15
|
+
const publicProfileSchema = z.object({
|
|
16
|
+
id: z.string(), displayName: z.string(), organization: z.string(), brand: brandSchema,
|
|
17
|
+
credentialRef: z.string(),
|
|
18
|
+
provider: z.object({
|
|
19
|
+
id: z.string(), displayName: z.string(),
|
|
20
|
+
models: z.array(z.object({ id: z.string(), name: z.string(), input: z.array(z.enum(['text', 'image'])) }).strict()),
|
|
21
|
+
}).strict(),
|
|
22
|
+
}).strict()
|
|
23
|
+
|
|
24
|
+
const accountSchema = z.object({
|
|
25
|
+
profileID: z.string(), displayName: z.string(), organization: z.string(), state: z.string(),
|
|
26
|
+
userName: z.string().optional(), affiliation: z.string().optional(), accessExpiresAt: z.string().optional(),
|
|
27
|
+
credentialRef: z.string(), credentialReady: z.boolean(), credentialState: z.string().optional(),
|
|
28
|
+
capabilities: z.array(z.string()).optional(),
|
|
29
|
+
}).strict()
|
|
30
|
+
|
|
31
|
+
export const runtimeModelSchema = z.object({
|
|
32
|
+
id: z.string().min(1).max(256), name: z.string().max(256).optional(),
|
|
33
|
+
upstreamModelID: z.string().max(256).optional(), contextWindow: z.number().int().positive().optional(),
|
|
34
|
+
maxTokens: z.number().int().positive().optional(),
|
|
35
|
+
input: z.array(z.enum(['text', 'image', 'audio', 'video'])).optional(),
|
|
36
|
+
reasoning: z.boolean().optional(), reasoningEfforts: z.record(z.string(), z.string().nullable()).optional(),
|
|
37
|
+
compat: z.record(z.string(), z.unknown()).optional(),
|
|
38
|
+
}).strict()
|
|
39
|
+
|
|
40
|
+
export const runtimeModelsSchema = z.array(runtimeModelSchema).max(128)
|
|
41
|
+
|
|
42
|
+
const managementProfileSchema = z.object({
|
|
43
|
+
id: z.string(), displayName: z.string(), organization: z.string(), baseURL: z.string(),
|
|
44
|
+
builtIn: z.boolean(), configured: z.boolean(), enabled: z.boolean(), providerID: z.string(),
|
|
45
|
+
runtime: z.object({
|
|
46
|
+
displayName: z.string(), modelSource: z.string().optional(), reasoning: z.string().optional(),
|
|
47
|
+
defaultContextWindow: z.number().int().positive().optional(), defaultMaxTokens: z.number().int().positive().optional(),
|
|
48
|
+
compat: z.record(z.string(), z.unknown()).optional(), models: runtimeModelsSchema,
|
|
49
|
+
}).strict(),
|
|
50
|
+
}).strict()
|
|
51
|
+
|
|
52
|
+
export const configurationResult = Object.freeze({
|
|
53
|
+
mode: 'strict', typeSymbol: `${pkg}#Configuration`,
|
|
54
|
+
schema: z.object({ schemaVersion: z.literal('dsh-oidc/v1alpha1'), uiMode: z.enum(['standard', 'models-only', 'external']), profiles: z.array(publicProfileSchema) }).strict(),
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
export const managementResult = Object.freeze({
|
|
58
|
+
mode: 'strict', typeSymbol: `${pkg}#ManagementConfiguration`,
|
|
59
|
+
schema: z.object({
|
|
60
|
+
schemaVersion: z.literal('dsh-oidc/management/v1alpha1'), mode: z.enum(['profile', 'native']),
|
|
61
|
+
activeProfileID: z.string(), restartRequired: z.boolean(),
|
|
62
|
+
capabilities: z.object({ manageProfiles: z.boolean(), manageModels: z.boolean(), restart: z.boolean() }).strict(),
|
|
63
|
+
profiles: z.array(managementProfileSchema),
|
|
64
|
+
}).strict(),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
export const restartResult = Object.freeze({
|
|
68
|
+
mode: 'strict', typeSymbol: `${pkg}#RestartResult`,
|
|
69
|
+
schema: z.object({ restarting: z.literal(true) }).strict(),
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
export const accountResult = Object.freeze({ mode: 'strict', typeSymbol: `${pkg}#AccountStatus`, schema: accountSchema })
|
|
73
|
+
|
|
74
|
+
export const beginResult = Object.freeze({
|
|
75
|
+
mode: 'strict', typeSymbol: `${pkg}#BeginResult`,
|
|
76
|
+
schema: z.union([
|
|
77
|
+
z.object({ mode: z.literal('redirect'), authorizationURL: z.string().url() }).strict(),
|
|
78
|
+
z.object({ mode: z.literal('completed'), status: accountSchema }).strict(),
|
|
79
|
+
]),
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
export function jsonParameter(name, schema, typeSymbol) {
|
|
83
|
+
return Object.freeze({ name, wire: name, source: 'json', codec: Object.freeze({ mode: 'strict', typeSymbol, schema }) })
|
|
84
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
accountResult, baseURLSchema, beginResult, configurationResult, jsonParameter, managementResult,
|
|
3
|
+
modelCatalogModeSchema, profileIdSchema, reconcileOptionsSchema, restartResult, runtimeModelsSchema,
|
|
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 options = () => jsonParameter('options', reconcileOptionsSchema, `${pkg}#ReconcileOptions`)
|
|
10
|
+
const baseURL = () => jsonParameter('baseURL', baseURLSchema, `${pkg}#BaseURL`)
|
|
11
|
+
const modelMode = () => jsonParameter('modelMode', modelCatalogModeSchema, `${pkg}#ModelCatalogMode`)
|
|
12
|
+
const models = () => jsonParameter('models', runtimeModelsSchema, `${pkg}#RuntimeModels`)
|
|
13
|
+
const descriptor = (method, parameters, result) => ({
|
|
14
|
+
id: `${pkg}#oidcAccounts/${method}`, service: 'oidcAccounts', namespace: 'oidcAccounts', method,
|
|
15
|
+
invocation: { kind: 'direct' }, parameters, result, sourceLocation: source,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const TYPERT = {
|
|
19
|
+
package: pkg, face: 'host', schemas: [],
|
|
20
|
+
invocations: [
|
|
21
|
+
descriptor('configuration', [], configurationResult),
|
|
22
|
+
descriptor('status', [profile()], accountResult),
|
|
23
|
+
descriptor('begin', [profile()], beginResult),
|
|
24
|
+
descriptor('reconcile', [profile(), options()], accountResult),
|
|
25
|
+
descriptor('logout', [profile()], accountResult),
|
|
26
|
+
descriptor('management', [], managementResult),
|
|
27
|
+
descriptor('activate', [profile()], managementResult),
|
|
28
|
+
descriptor('configure', [profile()], managementResult),
|
|
29
|
+
descriptor('addCustom', [baseURL()], managementResult),
|
|
30
|
+
descriptor('updateCustom', [profile(), baseURL()], managementResult),
|
|
31
|
+
descriptor('removeProfile', [profile()], managementResult),
|
|
32
|
+
descriptor('configureModels', [profile(), modelMode(), models()], managementResult),
|
|
33
|
+
descriptor('restart', [], restartResult),
|
|
34
|
+
],
|
|
35
|
+
model: { services: [], events: [], objects: [] },
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default TYPERT
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {
|
|
2
|
+
accountResult, baseURLSchema, beginResult, configurationResult, jsonParameter, managementResult,
|
|
3
|
+
modelCatalogModeSchema, profileIdSchema, reconcileOptionsSchema, restartResult, runtimeModelsSchema,
|
|
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 options = () => jsonParameter('options', reconcileOptionsSchema, `${pkg}#ReconcileOptions`)
|
|
10
|
+
const baseURL = () => jsonParameter('baseURL', baseURLSchema, `${pkg}#BaseURL`)
|
|
11
|
+
const modelMode = () => jsonParameter('modelMode', modelCatalogModeSchema, `${pkg}#ModelCatalogMode`)
|
|
12
|
+
const models = () => jsonParameter('models', runtimeModelsSchema, `${pkg}#RuntimeModels`)
|
|
13
|
+
const descriptor = (method, parameters, result) => ({
|
|
14
|
+
id: `${pkg}#oidcAccounts/${method}`, service: 'oidcAccounts', namespace: 'oidcAccounts', method,
|
|
15
|
+
invocation: { kind: 'direct' }, parameters, result, sourceLocation: source,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const TYPERT_REMOTE = {
|
|
19
|
+
package: pkg,
|
|
20
|
+
descriptors: [
|
|
21
|
+
descriptor('configuration', [], configurationResult),
|
|
22
|
+
descriptor('status', [profile()], accountResult),
|
|
23
|
+
descriptor('begin', [profile()], beginResult),
|
|
24
|
+
descriptor('reconcile', [profile(), options()], accountResult),
|
|
25
|
+
descriptor('logout', [profile()], accountResult),
|
|
26
|
+
descriptor('management', [], managementResult),
|
|
27
|
+
descriptor('activate', [profile()], managementResult),
|
|
28
|
+
descriptor('configure', [profile()], managementResult),
|
|
29
|
+
descriptor('addCustom', [baseURL()], managementResult),
|
|
30
|
+
descriptor('updateCustom', [profile(), baseURL()], managementResult),
|
|
31
|
+
descriptor('removeProfile', [profile()], managementResult),
|
|
32
|
+
descriptor('configureModels', [profile(), modelMode(), models()], managementResult),
|
|
33
|
+
descriptor('restart', [], restartResult),
|
|
34
|
+
],
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default TYPERT_REMOTE
|
package/package.json
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eduwork/dsh-oidc",
|
|
3
|
+
"version": "0.1.0-alpha.11",
|
|
4
|
+
"description": "Standards-based OIDC identity, enterprise API-key binding, declarative branding, and OpenAI-compatible model integration for DeepSeek Harness",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"deepseek-harness",
|
|
7
|
+
"dsh",
|
|
8
|
+
"dsh-plugin",
|
|
9
|
+
"oidc",
|
|
10
|
+
"openid-connect",
|
|
11
|
+
"enterprise",
|
|
12
|
+
"sso"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "lib/index.js",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./lib/index.js",
|
|
18
|
+
"./client": "./lib/client.js",
|
|
19
|
+
"./profile": "./lib/profile.js",
|
|
20
|
+
"./oidc": "./lib/oidc.js",
|
|
21
|
+
"./provider": "./lib/provider/index.js",
|
|
22
|
+
"./transforms": "./lib/provider/transforms.js",
|
|
23
|
+
"./typert": "./lib/typert.host.js",
|
|
24
|
+
"./remote": "./lib/typert.remote-client.js",
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"cordis.patch.yml",
|
|
29
|
+
"lib/**/*.js",
|
|
30
|
+
"docs/**/*.md",
|
|
31
|
+
"examples/**/*.json",
|
|
32
|
+
"schema/**/*.json",
|
|
33
|
+
"protocol/openapi.yaml",
|
|
34
|
+
"README.md",
|
|
35
|
+
"README.en.md",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"THIRD_PARTY_NOTICES.md",
|
|
38
|
+
"THIRD_PARTY_NOTICES.en.md",
|
|
39
|
+
"CHANGELOG.md",
|
|
40
|
+
"CHANGELOG.en.md"
|
|
41
|
+
],
|
|
42
|
+
"dsh": {
|
|
43
|
+
"bundle": {
|
|
44
|
+
"patch": "./cordis.patch.yml"
|
|
45
|
+
},
|
|
46
|
+
"client": {
|
|
47
|
+
"inject": [
|
|
48
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
49
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
51
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
52
|
+
"@deepseek-ai/dsh-client-ui-settings-models",
|
|
53
|
+
"@deepseek-ai/dsh-client-ui-sidebar",
|
|
54
|
+
"@deepseek-ai/dsh-client-ui-theme"
|
|
55
|
+
],
|
|
56
|
+
"platform": "web",
|
|
57
|
+
"immediately": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build:host": "node scripts/build-host.mjs",
|
|
62
|
+
"build:client": "tsdown",
|
|
63
|
+
"build": "npm run build:host && npm run build:client",
|
|
64
|
+
"typecheck": "tsc --noEmit",
|
|
65
|
+
"test": "node --test",
|
|
66
|
+
"test:coverage": "node --test --experimental-test-coverage",
|
|
67
|
+
"check:schemas": "node scripts/check-schemas.mjs",
|
|
68
|
+
"check:secrets": "node scripts/check-secrets.mjs",
|
|
69
|
+
"check:licenses": "node scripts/check-licenses.mjs",
|
|
70
|
+
"check:docs": "node scripts/check-doc-links.mjs",
|
|
71
|
+
"check:build": "node scripts/smoke-build.mjs",
|
|
72
|
+
"check": "npm run typecheck && npm run build && npm run check:build && npm test && npm run check:schemas && npm run check:docs && npm run check:secrets && npm run check:licenses && npm pack --dry-run",
|
|
73
|
+
"prepare": "npm run build"
|
|
74
|
+
},
|
|
75
|
+
"engines": {
|
|
76
|
+
"node": ">=22.0.0"
|
|
77
|
+
},
|
|
78
|
+
"publishConfig": {
|
|
79
|
+
"access": "public",
|
|
80
|
+
"provenance": true
|
|
81
|
+
},
|
|
82
|
+
"repository": {
|
|
83
|
+
"type": "git",
|
|
84
|
+
"url": "git+https://github.com/freedomkk-qfeng/dsh-oidc.git"
|
|
85
|
+
},
|
|
86
|
+
"bugs": {
|
|
87
|
+
"url": "https://github.com/freedomkk-qfeng/dsh-oidc/issues"
|
|
88
|
+
},
|
|
89
|
+
"homepage": "https://github.com/freedomkk-qfeng/dsh-oidc#readme",
|
|
90
|
+
"license": "MIT",
|
|
91
|
+
"dependencies": {
|
|
92
|
+
"zod": "4.4.3"
|
|
93
|
+
},
|
|
94
|
+
"peerDependencies": {
|
|
95
|
+
"@deepseek-ai/cordis": "4.0.2",
|
|
96
|
+
"@deepseek-ai/dsh-api-remotes": "0.1.2-alpha.2",
|
|
97
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.2-alpha.2",
|
|
98
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.1.2-alpha.2",
|
|
99
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.2-alpha.2",
|
|
100
|
+
"@deepseek-ai/dsh-client-ui-settings-models": "0.1.2-alpha.2",
|
|
101
|
+
"@deepseek-ai/dsh-client-ui-sidebar": "0.1.2-alpha.2",
|
|
102
|
+
"@deepseek-ai/dsh-client-ui-theme": "0.1.2-alpha.2",
|
|
103
|
+
"@deepseek-ai/dsh-credentials": "0.1.2-alpha.2",
|
|
104
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.2-alpha.2",
|
|
105
|
+
"@deepseek-ai/dsh-launch-environment": "0.1.2-alpha.2",
|
|
106
|
+
"@deepseek-ai/dsh-llm": "0.1.2-alpha.2",
|
|
107
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.1.2-alpha.2",
|
|
108
|
+
"@deepseek-ai/dsh-settings": "0.1.2-alpha.2",
|
|
109
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.2-alpha.2",
|
|
110
|
+
"@earendil-works/pi-ai": "^0.84.2",
|
|
111
|
+
"react": "^18.2.0"
|
|
112
|
+
},
|
|
113
|
+
"devDependencies": {
|
|
114
|
+
"@types/react": "^18.3.28",
|
|
115
|
+
"ajv": "^8.17.1",
|
|
116
|
+
"tsdown": "0.22.14",
|
|
117
|
+
"typescript": "^5.9.3",
|
|
118
|
+
"yaml": "^2.8.1"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
openapi: 3.1.0
|
|
2
|
+
info:
|
|
3
|
+
title: dsh-oidc Worker User Center Key Binding API
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: >-
|
|
6
|
+
The fixed worker-user-center-v1 protocol used by dsh-oidc to exchange an
|
|
7
|
+
OIDC Bearer access token for a short-lived or revocable model runtime
|
|
8
|
+
credential. Endpoint paths and wire field names are deliberately not
|
|
9
|
+
configurable. See docs/key-binding-protocol.md for normative requirements.
|
|
10
|
+
license:
|
|
11
|
+
name: MIT
|
|
12
|
+
identifier: MIT
|
|
13
|
+
servers:
|
|
14
|
+
- url: https://ai.example.edu/api/worker/v1
|
|
15
|
+
description: Example baseURL from an Enterprise Profile
|
|
16
|
+
security:
|
|
17
|
+
- oidcAccessToken: []
|
|
18
|
+
paths:
|
|
19
|
+
/bootstrap:
|
|
20
|
+
get:
|
|
21
|
+
operationId: bootstrap
|
|
22
|
+
summary: Read subject-independent integration state and credential lifecycle state
|
|
23
|
+
responses:
|
|
24
|
+
'200':
|
|
25
|
+
description: Current provider and runtime credential state
|
|
26
|
+
headers:
|
|
27
|
+
Cache-Control:
|
|
28
|
+
schema: { type: string }
|
|
29
|
+
description: MUST contain no-store
|
|
30
|
+
content:
|
|
31
|
+
application/json:
|
|
32
|
+
schema: { $ref: '#/components/schemas/BootstrapResponse' }
|
|
33
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
34
|
+
'403': { $ref: '#/components/responses/Forbidden' }
|
|
35
|
+
'429': { $ref: '#/components/responses/RateLimited' }
|
|
36
|
+
'5XX': { $ref: '#/components/responses/ServerError' }
|
|
37
|
+
/runtime-credential/provision:
|
|
38
|
+
post:
|
|
39
|
+
operationId: provisionRuntimeCredential
|
|
40
|
+
summary: Provision and return a runtime credential for the authenticated subject
|
|
41
|
+
parameters:
|
|
42
|
+
- $ref: '#/components/parameters/IdempotencyKey'
|
|
43
|
+
requestBody:
|
|
44
|
+
required: true
|
|
45
|
+
content:
|
|
46
|
+
application/json:
|
|
47
|
+
schema: { $ref: '#/components/schemas/RuntimeCredentialRequest' }
|
|
48
|
+
responses:
|
|
49
|
+
'200': { $ref: '#/components/responses/RuntimeCredential' }
|
|
50
|
+
'201': { $ref: '#/components/responses/RuntimeCredential' }
|
|
51
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
52
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
53
|
+
'403': { $ref: '#/components/responses/Forbidden' }
|
|
54
|
+
'409': { $ref: '#/components/responses/Conflict' }
|
|
55
|
+
'429': { $ref: '#/components/responses/RateLimited' }
|
|
56
|
+
'5XX': { $ref: '#/components/responses/ServerError' }
|
|
57
|
+
/runtime-credential/resolve:
|
|
58
|
+
post:
|
|
59
|
+
operationId: resolveRuntimeCredential
|
|
60
|
+
summary: Resolve the active runtime credential for the authenticated subject
|
|
61
|
+
requestBody:
|
|
62
|
+
required: true
|
|
63
|
+
content:
|
|
64
|
+
application/json:
|
|
65
|
+
schema: { $ref: '#/components/schemas/RuntimeCredentialRequest' }
|
|
66
|
+
responses:
|
|
67
|
+
'200': { $ref: '#/components/responses/RuntimeCredential' }
|
|
68
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
69
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
70
|
+
'403': { $ref: '#/components/responses/Forbidden' }
|
|
71
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
72
|
+
'429': { $ref: '#/components/responses/RateLimited' }
|
|
73
|
+
'5XX': { $ref: '#/components/responses/ServerError' }
|
|
74
|
+
/runtime-credential/renew:
|
|
75
|
+
post:
|
|
76
|
+
operationId: renewRuntimeCredential
|
|
77
|
+
summary: Rotate an expiring or expired runtime credential
|
|
78
|
+
parameters:
|
|
79
|
+
- $ref: '#/components/parameters/IdempotencyKey'
|
|
80
|
+
requestBody:
|
|
81
|
+
required: true
|
|
82
|
+
content:
|
|
83
|
+
application/json:
|
|
84
|
+
schema:
|
|
85
|
+
allOf:
|
|
86
|
+
- $ref: '#/components/schemas/RuntimeCredentialRequest'
|
|
87
|
+
- type: object
|
|
88
|
+
required: [api_key_id]
|
|
89
|
+
responses:
|
|
90
|
+
'200': { $ref: '#/components/responses/RuntimeCredential' }
|
|
91
|
+
'201': { $ref: '#/components/responses/RuntimeCredential' }
|
|
92
|
+
'400': { $ref: '#/components/responses/BadRequest' }
|
|
93
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
94
|
+
'403': { $ref: '#/components/responses/Forbidden' }
|
|
95
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
96
|
+
'409': { $ref: '#/components/responses/Conflict' }
|
|
97
|
+
'429': { $ref: '#/components/responses/RateLimited' }
|
|
98
|
+
'5XX': { $ref: '#/components/responses/ServerError' }
|
|
99
|
+
components:
|
|
100
|
+
securitySchemes:
|
|
101
|
+
oidcAccessToken:
|
|
102
|
+
type: http
|
|
103
|
+
scheme: bearer
|
|
104
|
+
bearerFormat: JWT or opaque OIDC access token
|
|
105
|
+
description: >-
|
|
106
|
+
Access token issued to the public OIDC client declared in the matching
|
|
107
|
+
Enterprise Profile. The server validates issuer, audience, expiry,
|
|
108
|
+
required scopes, and the authenticated subject.
|
|
109
|
+
parameters:
|
|
110
|
+
IdempotencyKey:
|
|
111
|
+
name: Idempotency-Key
|
|
112
|
+
in: header
|
|
113
|
+
required: true
|
|
114
|
+
description: Opaque UUID generated for one logical mutating operation.
|
|
115
|
+
schema:
|
|
116
|
+
type: string
|
|
117
|
+
format: uuid
|
|
118
|
+
schemas:
|
|
119
|
+
BootstrapResponse:
|
|
120
|
+
type: object
|
|
121
|
+
additionalProperties: false
|
|
122
|
+
required: [provider, capabilities, runtime_credential]
|
|
123
|
+
properties:
|
|
124
|
+
protocol_version:
|
|
125
|
+
type: string
|
|
126
|
+
const: worker-user-center/v1
|
|
127
|
+
description: Optional during v1 adoption; if present it MUST equal this value.
|
|
128
|
+
subject:
|
|
129
|
+
$ref: '#/components/schemas/InformationalSubject'
|
|
130
|
+
provider:
|
|
131
|
+
type: object
|
|
132
|
+
additionalProperties: false
|
|
133
|
+
required: [id]
|
|
134
|
+
properties:
|
|
135
|
+
id:
|
|
136
|
+
type: string
|
|
137
|
+
pattern: '^[a-z][a-z0-9-]{0,63}$'
|
|
138
|
+
capabilities:
|
|
139
|
+
type: array
|
|
140
|
+
maxItems: 256
|
|
141
|
+
uniqueItems: true
|
|
142
|
+
items: { type: string, minLength: 1, maxLength: 128 }
|
|
143
|
+
runtime_credential:
|
|
144
|
+
$ref: '#/components/schemas/RuntimeCredentialState'
|
|
145
|
+
InformationalSubject:
|
|
146
|
+
type: object
|
|
147
|
+
additionalProperties: true
|
|
148
|
+
description: >-
|
|
149
|
+
Optional management-plane display data. dsh-oidc MUST NOT use it to
|
|
150
|
+
replace the identity established by the OIDC ID Token and UserInfo.
|
|
151
|
+
properties:
|
|
152
|
+
sub: { type: string }
|
|
153
|
+
name: { type: string }
|
|
154
|
+
affiliation: { type: string }
|
|
155
|
+
RuntimeCredentialState:
|
|
156
|
+
type: object
|
|
157
|
+
additionalProperties: false
|
|
158
|
+
required: [status]
|
|
159
|
+
properties:
|
|
160
|
+
status:
|
|
161
|
+
enum: [missing, active, expiring, expired, suspended, unavailable]
|
|
162
|
+
api_key_id:
|
|
163
|
+
type: string
|
|
164
|
+
minLength: 1
|
|
165
|
+
maxLength: 256
|
|
166
|
+
expires_at:
|
|
167
|
+
type: string
|
|
168
|
+
format: date-time
|
|
169
|
+
provisioning:
|
|
170
|
+
type: object
|
|
171
|
+
additionalProperties: false
|
|
172
|
+
properties:
|
|
173
|
+
allowed: { type: boolean }
|
|
174
|
+
reason: { type: string, maxLength: 500 }
|
|
175
|
+
RuntimeCredentialRequest:
|
|
176
|
+
type: object
|
|
177
|
+
additionalProperties: false
|
|
178
|
+
required: [provider_id]
|
|
179
|
+
properties:
|
|
180
|
+
provider_id:
|
|
181
|
+
type: string
|
|
182
|
+
pattern: '^[a-z][a-z0-9-]{0,63}$'
|
|
183
|
+
api_key_id:
|
|
184
|
+
type: string
|
|
185
|
+
minLength: 1
|
|
186
|
+
maxLength: 256
|
|
187
|
+
RuntimeCredentialResponse:
|
|
188
|
+
type: object
|
|
189
|
+
additionalProperties: false
|
|
190
|
+
required: [provider_id, api_key, status]
|
|
191
|
+
properties:
|
|
192
|
+
provider_id:
|
|
193
|
+
type: string
|
|
194
|
+
pattern: '^[a-z][a-z0-9-]{0,63}$'
|
|
195
|
+
api_key:
|
|
196
|
+
type: string
|
|
197
|
+
minLength: 1
|
|
198
|
+
maxLength: 16384
|
|
199
|
+
description: Secret value. It MUST appear only in this TLS response and MUST NOT be logged.
|
|
200
|
+
api_key_id:
|
|
201
|
+
type: string
|
|
202
|
+
minLength: 1
|
|
203
|
+
maxLength: 256
|
|
204
|
+
status:
|
|
205
|
+
const: active
|
|
206
|
+
expires_at:
|
|
207
|
+
type: string
|
|
208
|
+
format: date-time
|
|
209
|
+
Problem:
|
|
210
|
+
type: object
|
|
211
|
+
additionalProperties: true
|
|
212
|
+
required: [type, title, status]
|
|
213
|
+
properties:
|
|
214
|
+
type: { type: string, format: uri-reference }
|
|
215
|
+
title: { type: string }
|
|
216
|
+
status: { type: integer, minimum: 400, maximum: 599 }
|
|
217
|
+
detail: { type: string }
|
|
218
|
+
instance: { type: string, format: uri-reference }
|
|
219
|
+
code: { type: string, description: Stable machine-readable service code }
|
|
220
|
+
responses:
|
|
221
|
+
RuntimeCredential:
|
|
222
|
+
description: Active runtime credential
|
|
223
|
+
headers:
|
|
224
|
+
Cache-Control:
|
|
225
|
+
schema: { type: string }
|
|
226
|
+
description: MUST contain no-store
|
|
227
|
+
Pragma:
|
|
228
|
+
schema: { type: string }
|
|
229
|
+
description: SHOULD be no-cache for compatibility
|
|
230
|
+
content:
|
|
231
|
+
application/json:
|
|
232
|
+
schema: { $ref: '#/components/schemas/RuntimeCredentialResponse' }
|
|
233
|
+
BadRequest:
|
|
234
|
+
description: Invalid request
|
|
235
|
+
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
|
|
236
|
+
Unauthorized:
|
|
237
|
+
description: Missing, invalid, expired, or wrong-audience access token
|
|
238
|
+
headers:
|
|
239
|
+
WWW-Authenticate: { schema: { type: string } }
|
|
240
|
+
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
|
|
241
|
+
Forbidden:
|
|
242
|
+
description: Authenticated subject lacks the required scope or organization entitlement
|
|
243
|
+
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
|
|
244
|
+
NotFound:
|
|
245
|
+
description: Requested runtime credential does not exist
|
|
246
|
+
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
|
|
247
|
+
Conflict:
|
|
248
|
+
description: Idempotency conflict or incompatible credential lifecycle state
|
|
249
|
+
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
|
|
250
|
+
RateLimited:
|
|
251
|
+
description: Rate limit exceeded
|
|
252
|
+
headers:
|
|
253
|
+
Retry-After: { schema: { type: string } }
|
|
254
|
+
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
|
|
255
|
+
ServerError:
|
|
256
|
+
description: Server-side failure with no secret material
|
|
257
|
+
content: { application/problem+json: { schema: { $ref: '#/components/schemas/Problem' } } }
|