@doitian/dsh-provider-aliyun 0.1.0
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/LICENSE +21 -0
- package/README.md +209 -0
- package/cordis.patch.yml +12 -0
- package/lib/adapter.js +133 -0
- package/lib/catalog.js +123 -0
- package/lib/index.js +133 -0
- package/lib/models.js +48 -0
- package/lib/provider.js +53 -0
- package/package.json +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 doitian
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# @doitian/dsh-provider-aliyun
|
|
2
|
+
|
|
3
|
+
Aliyun **DashScope (Bailian)** as a model provider for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).
|
|
4
|
+
|
|
5
|
+
The plugin registers one provider route, `aliyun`, and **owns it outright** — endpoint, credential reference, protocol, and the Qwen model catalog. The catalog is a file in this package, so the model list moves forward with `pnpm update` and never has to be written into a profile.
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
provider: aliyun
|
|
9
|
+
model: qwen3.8-max
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Why a plugin and not a config snippet
|
|
13
|
+
|
|
14
|
+
An OpenAI-compatible provider *can* be added to DSH with configuration alone — declare a route under `llm-pi-ai`'s `providers` and hand-write its model list. That works, and then the model list lives in your profile forever: every new Qwen release is a hand edit, and a profile patch replaces an entry's whole `config`, so nothing can merge models into it later.
|
|
15
|
+
|
|
16
|
+
This package takes the other route. It registers its own route on the LLM seam and ships the catalog as data:
|
|
17
|
+
|
|
18
|
+
| | Config-only route | This plugin |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| Where the model list lives | your `cordis.patch.yml` | `lib/catalog.js` in this package |
|
|
21
|
+
| Adding a new Qwen model | edit your profile | `pnpm update` |
|
|
22
|
+
| Can a profile patch shadow it | — | no |
|
|
23
|
+
| Your profile config | the whole provider block | nothing |
|
|
24
|
+
|
|
25
|
+
The trade-off is honest: the plugin depends on an internal shape of the adapter it reuses (see [How it works](#how-it-works)), which a config-only route does not.
|
|
26
|
+
|
|
27
|
+
## Requirements
|
|
28
|
+
|
|
29
|
+
- DeepSeek Harness `0.2.0-rc.2` with the `@deepseek-ai/dsh-base` bundle, which mounts the LLM seam this plugin registers on.
|
|
30
|
+
- An Aliyun DashScope / Bailian API key.
|
|
31
|
+
- **No `aliyun` route configured through `llm-pi-ai`.** Two adapters cannot declare the same route: mounting this plugin while a profile still configures one fails with `configurable provider "aliyun" is already declared`. Remove that block from your profile patch first — that is the whole point of the plugin.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
**Desktop app.** Sidebar **Plugins** → install `@doitian/dsh-provider-aliyun`. A newly installed bundle is enabled by default.
|
|
36
|
+
|
|
37
|
+
**Any other profile.** `dsh plugin` forwards its arguments to pnpm in the profile directory:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
dsh plugin --profile <name> add @doitian/dsh-provider-aliyun
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Then make sure the bundle is *selected* — a raw `add` installs the dependency but does not add it to `dsh.profile.bundles` in the profile's `package.json`:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"dsh": {
|
|
48
|
+
"profile": {
|
|
49
|
+
"bundles": ["@deepseek-ai/dsh-base", "@doitian/dsh-provider-aliyun"]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> The `dsh plugin` CLI refuses the `desktop` profile (`profile "desktop" is managed exclusively by the Electron application`); use the Plugins page there.
|
|
56
|
+
|
|
57
|
+
## Configure the API key
|
|
58
|
+
|
|
59
|
+
Nothing is needed to make the route exist, and no key is stored in this package or in any profile patch. The route resolves a credential reference per request:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
apiKeyEnv: ALIYUN_API_KEY # the default
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Settings → Models → Aliyun DashScope** → paste the key into the **API key** field. It is written write-only to `$DSH_HOME/.credentials.yaml` under `ALIYUN_API_KEY`, and never read back.
|
|
66
|
+
|
|
67
|
+
Or export `ALIYUN_API_KEY` in the environment that launches DSH. Until the key resolves, selecting an Aliyun model fails immediately with `MISSING_CREDENTIAL`, before any network I/O.
|
|
68
|
+
|
|
69
|
+
### Endpoints
|
|
70
|
+
|
|
71
|
+
| Region | Endpoint |
|
|
72
|
+
|---|---|
|
|
73
|
+
| Mainland China (default) | `https://dashscope.aliyuncs.com/compatible-mode/v1` |
|
|
74
|
+
| International | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1` |
|
|
75
|
+
|
|
76
|
+
Set it in the Models page, or by adding `baseURL` to this entry's `config`:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
- id: llm-aliyun
|
|
80
|
+
name: '@doitian/dsh-provider-aliyun'
|
|
81
|
+
config:
|
|
82
|
+
baseURL: https://dashscope-intl.aliyuncs.com/compatible-mode/v1
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Models
|
|
86
|
+
|
|
87
|
+
`lib/catalog.js` is the catalog. It ships six Qwen models:
|
|
88
|
+
|
|
89
|
+
`qwen3.8-max`, `qwen3.8-flash`, `qwen3.7-max`, `qwen3.7-plus`, `qwen3.6-plus`, `qwen3.6-flash`
|
|
90
|
+
|
|
91
|
+
Capacities and thinking levels mirror the facts Aliyun publishes for its Qwen lineup. A catalog entry states only what is true of the *model* — id, display name, context window, output cap, accepted modalities, and thinking levels. The route, endpoint, protocol, and compatibility switches are filled in from configuration by `lib/models.js`, so updating the catalog never means touching pi-ai plumbing.
|
|
92
|
+
|
|
93
|
+
**Verify capacities against your endpoint when you upgrade.** A wrong `contextWindow` is the failure that hurts, because the harness trusts it when it decides to compact history.
|
|
94
|
+
|
|
95
|
+
### Updating the catalog
|
|
96
|
+
|
|
97
|
+
Edit `lib/catalog.js` and publish. Consumers get the new list with `pnpm update`.
|
|
98
|
+
|
|
99
|
+
### Overriding models for one profile
|
|
100
|
+
|
|
101
|
+
`models` is a normal configuration field whose default is the shipped catalog, so a profile can replace it without forking:
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
- id: llm-aliyun
|
|
105
|
+
name: '@doitian/dsh-provider-aliyun'
|
|
106
|
+
config:
|
|
107
|
+
models:
|
|
108
|
+
- id: qwen3.8-max
|
|
109
|
+
name: Qwen3.8 Max
|
|
110
|
+
contextWindow: 1000000
|
|
111
|
+
maxTokens: 131072
|
|
112
|
+
input: [text, image]
|
|
113
|
+
reasoning: true
|
|
114
|
+
thinkingLevelMap: { low: low, medium: medium, high: high }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The Models page edits the same field, showing the shipped catalog as inherited rows until the first edit materializes an override.
|
|
118
|
+
|
|
119
|
+
### Thinking
|
|
120
|
+
|
|
121
|
+
Aliyun turns thinking on with a boolean `enable_thinking` rather than a reasoning-effort field, which is what `compat.thinkingFormat: 'qwen'` sends, and `supportsReasoningEffort: false` keeps an effort value off the wire where this endpoint would refuse it. The selected level still drives the boolean, so `off` genuinely turns thinking off.
|
|
122
|
+
|
|
123
|
+
`thinkingLevelMap` on each catalog entry declares which levels a model offers and the wire spelling of each. A level left out is not offered; `off` is offered unless it is named in the map.
|
|
124
|
+
|
|
125
|
+
## Uninstall
|
|
126
|
+
|
|
127
|
+
Remove it from `dsh.profile.bundles` (or delete the row on the Plugins page), then:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
dsh plugin --profile <name> remove @doitian/dsh-provider-aliyun
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The credential in `$DSH_HOME/.credentials.yaml` is left untouched. Deleting the route on the Models page removes it only when its reference is exactly the page-derived `ALIYUN_API_KEY`; a custom reference is retained deliberately, because the page cannot prove it owns it.
|
|
134
|
+
|
|
135
|
+
## How it works
|
|
136
|
+
|
|
137
|
+
`package.json` declares a bundle patch:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{ "dsh": { "bundle": { "patch": "./cordis.patch.yml" } } }
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
and the patch inserts this package's own plugin entry:
|
|
144
|
+
|
|
145
|
+
```yaml
|
|
146
|
+
- insert:
|
|
147
|
+
- id: llm-aliyun
|
|
148
|
+
name: '@doitian/dsh-provider-aliyun'
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
On mount, `lib/index.js` registers two things on the LLM seam: the `aliyun` route with an adapter, and a configurable-provider directory entry — the latter is what gives the route a row, and an API-key field, on the Models page.
|
|
152
|
+
|
|
153
|
+
The adapter is `PiAiAdapter`, exported by `@deepseek-ai/dsh-llm-pi-ai`. It owns the hard part — harness history into pi-ai context, pi-ai events into harness stream chunks, image budgets, replay metadata, idle watchdogs — and reusing it is what keeps this package a catalog plus a few dozen lines instead of a second adapter implementation.
|
|
154
|
+
|
|
155
|
+
**The one thing to know if you maintain this.** `PiAiAdapter` is driven by a route's *resolved profile*, a shape its package does not export as a type; `lib/adapter.js` reproduces it and documents every field it must carry. Model metadata is not read from that profile — it comes from the pi-ai model descriptors built out of the catalog, which is why the catalog can live here at all. A DSH upgrade that starts reading a new profile field breaks this plugin. `test/adapter.test.mjs` drives the real published adapter against the factory, so that break shows up as a failing test rather than as a broken route in someone's profile.
|
|
156
|
+
|
|
157
|
+
The engine is pinned to the harness's own pi-ai: the dependency is `^0.87.1`, which resolves to exactly the `0.87.1` the harness installs, so both share one copy.
|
|
158
|
+
|
|
159
|
+
## Development
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npm install
|
|
163
|
+
npm run validate # manifest, patch wiring, catalog integrity
|
|
164
|
+
npm test # drives the real PiAiAdapter against the catalog
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`scripts/validate.mjs` catches what would otherwise fail silently: a tarball that drops the patch, a patch that names the wrong package, a duplicate model id, a non-integer capacity, a level map pi-ai would read as offering nothing.
|
|
168
|
+
|
|
169
|
+
`npm test` needs the peer packages installed — that is the point, since it exercises the real adapter rather than a stub. A live request is out of scope: that needs a real key and endpoint.
|
|
170
|
+
|
|
171
|
+
## Publishing
|
|
172
|
+
|
|
173
|
+
Publishing uses **npm trusted publishing** (OIDC) from `.github/workflows/publish.yml`. There is no npm token anywhere: no `NODE_AUTH_TOKEN`, no `NPM_TOKEN`, no secret. The workflow declares `id-token: write` and runs `npm publish`, which exchanges that OIDC identity for a short-lived publish token.
|
|
174
|
+
|
|
175
|
+
One-time setup, as the package owner. npm 11.6 or later creates the publisher from the CLI — the same operation as the website form, without the form:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npm login # as doitian
|
|
179
|
+
|
|
180
|
+
npm trust github @doitian/dsh-provider-aliyun \
|
|
181
|
+
--file publish.yml \
|
|
182
|
+
--repo doitian/dsh-provider-aliyun \
|
|
183
|
+
--allow-publish
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`--allow-publish` is required: it grants the `CREATE_PACKAGE` permission, and the command refuses without it or `--allow-stage-publish`. It infers `owner/repo` from `package.json` when `--repo` is omitted, warns when the two disagree, requires 2FA, and prompts for an OTP. Add `--dry-run` first to see exactly what it would create without committing it.
|
|
187
|
+
|
|
188
|
+
`--environment` is deliberately omitted, matching the publish job, which declares no `environment:`.
|
|
189
|
+
|
|
190
|
+
If the registry reports the package as missing, a trusted publisher cannot be attached to a name that does not exist yet: publish `0.1.0` once by hand, then run the command above and let the workflow own every release after that.
|
|
191
|
+
|
|
192
|
+
Then release:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
npm version patch
|
|
196
|
+
git push --follow-tags
|
|
197
|
+
gh release create v0.1.1 --generate-notes
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The publish job requires the release tag to match `package.json` (`v0.1.1` ↔ `0.1.1`) and re-runs validation and tests before uploading.
|
|
201
|
+
|
|
202
|
+
Two things that will bite when editing the workflow:
|
|
203
|
+
|
|
204
|
+
- **`pnpm publish` does not work here.** It performs no OIDC exchange. Use `npm publish`.
|
|
205
|
+
- **Pushing `.github/workflows/` needs the `workflow` token scope.** An OAuth token without it is rejected for workflow files even though it can push everything else; add the scope with `gh auth refresh -h github.com -s workflow`, or use an SSH remote, which is not scope-limited.
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Aliyun DashScope (Bailian) provider bundle for DeepSeek Harness.
|
|
2
|
+
#
|
|
3
|
+
# This bundle inserts its own plugin entry. The endpoint, the credential
|
|
4
|
+
# reference, and the Qwen model catalog are all defaults inside the package, so
|
|
5
|
+
# nothing about the models belongs in a profile — the catalog moves forward with
|
|
6
|
+
# `pnpm update`, and no profile patch can shadow it.
|
|
7
|
+
#
|
|
8
|
+
# Configure it on the Models page, or by adding fields under this entry's
|
|
9
|
+
# `config` (displayName, apiKeyEnv, baseURL, models, reasoning, headers).
|
|
10
|
+
- insert:
|
|
11
|
+
- id: llm-aliyun
|
|
12
|
+
name: '@doitian/dsh-provider-aliyun'
|
package/lib/adapter.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire the shipped catalog into the harness LLM seam.
|
|
3
|
+
*
|
|
4
|
+
* The engine is `PiAiAdapter`, exported by `@deepseek-ai/dsh-llm-pi-ai`. It
|
|
5
|
+
* owns the hard part — harness history into pi-ai context, pi-ai events into
|
|
6
|
+
* harness stream chunks, image budgets, replay metadata, idle watchdogs — and
|
|
7
|
+
* is driven entirely through the `config` object below. Reusing it is what
|
|
8
|
+
* keeps this package a catalog plus a few dozen lines instead of a second
|
|
9
|
+
* adapter implementation.
|
|
10
|
+
*
|
|
11
|
+
* ## The contract this file depends on
|
|
12
|
+
*
|
|
13
|
+
* `PiAiAdapter` consumes a route's *resolved profile*. That type is not part of
|
|
14
|
+
* the package's exported surface: it is the shape `resolveProfiles` produces
|
|
15
|
+
* internally, and it is reproduced here. The fields, all of them, are:
|
|
16
|
+
*
|
|
17
|
+
* provider, displayName, apiKeyEnv, piProvider, catalogError, modelErrors,
|
|
18
|
+
* configuredMaxTokens, reasoning, thinkingBudgets, retryPolicy, headers,
|
|
19
|
+
* timeoutMs, websocketConnectTimeoutMs, streamIdleTimeoutMs, transport,
|
|
20
|
+
* cacheRetention, maxRequestImageBytes, requestImagePixelBudget,
|
|
21
|
+
* requestImageMaxBytes
|
|
22
|
+
*
|
|
23
|
+
* Model metadata — context window, modalities, thinking levels — is *not* read
|
|
24
|
+
* from the profile. It is read from the pi-ai model descriptors inside
|
|
25
|
+
* `piProvider`, which is why the catalog can live in this package.
|
|
26
|
+
*
|
|
27
|
+
* The risk this carries: a DSH upgrade that starts reading a new profile field
|
|
28
|
+
* breaks this plugin. `test/adapter.test.mjs` drives the real `PiAiAdapter`
|
|
29
|
+
* against this factory so that such a break shows up as a failing test rather
|
|
30
|
+
* than as a broken route in someone's profile.
|
|
31
|
+
*/
|
|
32
|
+
import { PiAiAdapter } from '@deepseek-ai/dsh-llm-pi-ai'
|
|
33
|
+
import { openAICompletionsApi } from '@earendil-works/pi-ai/api/openai-completions.lazy'
|
|
34
|
+
|
|
35
|
+
import { QWEN_COMPAT, ROUTE } from './catalog.js'
|
|
36
|
+
import { materializeModels } from './models.js'
|
|
37
|
+
import { createPiProvider } from './provider.js'
|
|
38
|
+
|
|
39
|
+
/** Idle bound on one outstanding stream read, matching the adapter's own default. */
|
|
40
|
+
export const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300_000
|
|
41
|
+
|
|
42
|
+
/** Aggregate base64 image payload a request may retain before offload is required. */
|
|
43
|
+
export const DEFAULT_MAX_REQUEST_IMAGE_BYTES = 20 * 1024 * 1024
|
|
44
|
+
|
|
45
|
+
/** Total-pixel budget for each deterministic request image. */
|
|
46
|
+
export const DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET = 4 * 1024 * 1024
|
|
47
|
+
|
|
48
|
+
/** Encoded-byte target for each request image before base64 expansion. */
|
|
49
|
+
export const DEFAULT_REQUEST_IMAGE_MAX_BYTES = 1024 * 1024
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build the resolved profile for this route.
|
|
53
|
+
*
|
|
54
|
+
* Everything is computed once, when the plugin mounts: the configuration
|
|
55
|
+
* carries no volatile field, so a configuration change remounts the plugin and
|
|
56
|
+
* rebuilds this. That keeps `profiles()` returning one stable map, which is
|
|
57
|
+
* what the adapter's snapshot memoization compares by identity.
|
|
58
|
+
*
|
|
59
|
+
* @param {object} options - route facts.
|
|
60
|
+
* @param {string} options.displayName - label for selectors.
|
|
61
|
+
* @param {string} options.apiKeyEnv - credential reference to resolve per request.
|
|
62
|
+
* @param {string} options.baseURL - endpoint for every model on the route.
|
|
63
|
+
* @param {readonly import('./catalog.js').AliyunModel[]} options.models - the shipped catalog.
|
|
64
|
+
* @param {string} [options.reasoning] - route default thinking level.
|
|
65
|
+
* @param {Record<string, string>} [options.headers] - extra request headers.
|
|
66
|
+
* @returns {object} the resolved profile.
|
|
67
|
+
*/
|
|
68
|
+
export function buildAliyunProfile(options) {
|
|
69
|
+
const models = materializeModels({
|
|
70
|
+
catalog: options.models,
|
|
71
|
+
provider: ROUTE,
|
|
72
|
+
baseUrl: options.baseURL,
|
|
73
|
+
compat: QWEN_COMPAT,
|
|
74
|
+
})
|
|
75
|
+
return {
|
|
76
|
+
provider: ROUTE,
|
|
77
|
+
displayName: options.displayName,
|
|
78
|
+
apiKeyEnv: options.apiKeyEnv,
|
|
79
|
+
piProvider: createPiProvider({
|
|
80
|
+
id: ROUTE,
|
|
81
|
+
name: options.displayName,
|
|
82
|
+
baseUrl: options.baseURL,
|
|
83
|
+
models,
|
|
84
|
+
api: openAICompletionsApi(),
|
|
85
|
+
}),
|
|
86
|
+
// `piProvider` is always defined here, so the adapter never consults the
|
|
87
|
+
// catalog diagnostic; it exists to keep the profile shape complete.
|
|
88
|
+
catalogError: undefined,
|
|
89
|
+
modelErrors: new Map(),
|
|
90
|
+
// Empty on purpose: the adapter reports these as *explicit* per-model caps,
|
|
91
|
+
// and the catalog's own `maxTokens` already governs the request.
|
|
92
|
+
configuredMaxTokens: new Map(),
|
|
93
|
+
reasoning: options.reasoning,
|
|
94
|
+
headers: options.headers,
|
|
95
|
+
streamIdleTimeoutMs: DEFAULT_STREAM_IDLE_TIMEOUT_MS,
|
|
96
|
+
maxRequestImageBytes: DEFAULT_MAX_REQUEST_IMAGE_BYTES,
|
|
97
|
+
requestImagePixelBudget: DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET,
|
|
98
|
+
requestImageMaxBytes: DEFAULT_REQUEST_IMAGE_MAX_BYTES,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Create the adapter for this route.
|
|
104
|
+
*
|
|
105
|
+
* `auth` is deliberately omitted. The harness resolves the credential reference
|
|
106
|
+
* itself and hands the key to pi-ai as the stream's `apiKey` option, so the
|
|
107
|
+
* credential stores and OAuth context the adapter would otherwise carry are
|
|
108
|
+
* never consulted; `createModels` falls back to in-memory ones.
|
|
109
|
+
*
|
|
110
|
+
* @param {object} options - route facts plus the harness callbacks.
|
|
111
|
+
* @param {string} options.displayName - label for selectors.
|
|
112
|
+
* @param {string} options.apiKeyEnv - credential reference to resolve per request.
|
|
113
|
+
* @param {string} options.baseURL - endpoint for every model on the route.
|
|
114
|
+
* @param {readonly import('./catalog.js').AliyunModel[]} options.models - the shipped catalog.
|
|
115
|
+
* @param {string} [options.reasoning] - route default thinking level.
|
|
116
|
+
* @param {Record<string, string>} [options.headers] - extra request headers.
|
|
117
|
+
* @param {(provider: string, profile: object) => Promise<string | undefined>} options.resolveApiKey - credential resolver.
|
|
118
|
+
* @param {() => object | undefined} [options.resolveAttachments] - durable attachment service accessor.
|
|
119
|
+
* @param {(attachments: object, ref: object) => unknown} [options.resolveImageAccess] - image access resolver.
|
|
120
|
+
* @param {(event: object) => void} [options.onReplayDegrade] - replay degradation reporter.
|
|
121
|
+
* @returns {PiAiAdapter} the adapter to register on the LLM seam.
|
|
122
|
+
*/
|
|
123
|
+
export function createAliyunAdapter(options) {
|
|
124
|
+
const profile = buildAliyunProfile(options)
|
|
125
|
+
const profiles = () => new Map([[ROUTE, profile]])
|
|
126
|
+
return new PiAiAdapter({
|
|
127
|
+
profiles,
|
|
128
|
+
resolveApiKey: options.resolveApiKey,
|
|
129
|
+
resolveAttachments: options.resolveAttachments,
|
|
130
|
+
resolveImageAccess: options.resolveImageAccess,
|
|
131
|
+
onReplayDegrade: options.onReplayDegrade,
|
|
132
|
+
})
|
|
133
|
+
}
|
package/lib/catalog.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Aliyun DashScope (Bailian) model catalog this plugin ships.
|
|
3
|
+
*
|
|
4
|
+
* This file is the whole point of the package: the model list lives here, in
|
|
5
|
+
* the plugin, so `pnpm update` refreshes it. Nothing about it needs to be
|
|
6
|
+
* written into a profile, and no profile patch can shadow it.
|
|
7
|
+
*
|
|
8
|
+
* It is plain data on purpose. {@link ./models.js} turns each entry into the
|
|
9
|
+
* pi-ai model descriptor the adapter dispatches with, filling in the route,
|
|
10
|
+
* endpoint, protocol, and compatibility switches from configuration — so an
|
|
11
|
+
* entry here states only what is true of the *model*, never of the deployment.
|
|
12
|
+
*
|
|
13
|
+
* The entries mirror the facts Aliyun publishes for its Qwen lineup. Verify
|
|
14
|
+
* them against your endpoint when you upgrade; a wrong `contextWindow` is the
|
|
15
|
+
* failure that hurts, because the harness trusts it when it compacts history.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Route name every request selects with `GenerateOptions.provider`. */
|
|
19
|
+
export const ROUTE = 'aliyun'
|
|
20
|
+
|
|
21
|
+
/** Label shown by provider selectors. */
|
|
22
|
+
export const DISPLAY_NAME = 'Aliyun DashScope'
|
|
23
|
+
|
|
24
|
+
/** Mainland-China DashScope endpoint, OpenAI-compatible mode. */
|
|
25
|
+
export const DEFAULT_BASE_URL = 'https://dashscope.aliyuncs.com/compatible-mode/v1'
|
|
26
|
+
|
|
27
|
+
/** International Model Studio endpoint. */
|
|
28
|
+
export const INTL_BASE_URL = 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1'
|
|
29
|
+
|
|
30
|
+
/** Credential reference the route resolves through the harness credential seam. */
|
|
31
|
+
export const DEFAULT_API_KEY_ENV = 'ALIYUN_API_KEY'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Wire compatibility for Aliyun's OpenAI-compatible endpoint.
|
|
35
|
+
*
|
|
36
|
+
* `thinkingFormat: "qwen"` is the load-bearing switch: Aliyun turns thinking on
|
|
37
|
+
* with a boolean `enable_thinking` rather than a reasoning-effort field, and
|
|
38
|
+
* that is what pi-ai's `qwen` format sends. `supportsReasoningEffort: false`
|
|
39
|
+
* keeps an effort value off the wire, where this endpoint would reject it,
|
|
40
|
+
* while the selected level still drives the boolean. The remaining switches
|
|
41
|
+
* describe what the endpoint does not implement, so the request does not carry
|
|
42
|
+
* fields it would refuse.
|
|
43
|
+
*/
|
|
44
|
+
export const QWEN_COMPAT = Object.freeze({
|
|
45
|
+
thinkingFormat: 'qwen',
|
|
46
|
+
supportsReasoningEffort: false,
|
|
47
|
+
supportsDeveloperRole: false,
|
|
48
|
+
supportsStore: false,
|
|
49
|
+
supportsStrictMode: true,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* One shipped model.
|
|
54
|
+
*
|
|
55
|
+
* @typedef {object} AliyunModel
|
|
56
|
+
* @property {string} id - request id, and the id the harness records.
|
|
57
|
+
* @property {string} name - display name for selectors.
|
|
58
|
+
* @property {number} contextWindow - total tokens the model accepts.
|
|
59
|
+
* @property {number} maxTokens - output cap for one response.
|
|
60
|
+
* @property {string[]} input - accepted modalities, `text` and/or `image`.
|
|
61
|
+
* @property {boolean} reasoning - whether the model can think.
|
|
62
|
+
* @property {Record<string, string>} [thinkingLevelMap] - selectable thinking
|
|
63
|
+
* levels and the wire spelling each one sends. A level left out is not
|
|
64
|
+
* offered; `off` is offered unless it is named here.
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/** @type {readonly AliyunModel[]} */
|
|
68
|
+
export const ALIYUN_MODELS = Object.freeze([
|
|
69
|
+
{
|
|
70
|
+
id: 'qwen3.8-max',
|
|
71
|
+
name: 'Qwen3.8 Max',
|
|
72
|
+
contextWindow: 1_000_000,
|
|
73
|
+
maxTokens: 131_072,
|
|
74
|
+
input: ['text', 'image'],
|
|
75
|
+
reasoning: true,
|
|
76
|
+
thinkingLevelMap: { low: 'low', medium: 'medium', high: 'high' },
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'qwen3.8-flash',
|
|
80
|
+
name: 'Qwen3.8 Flash',
|
|
81
|
+
contextWindow: 1_000_000,
|
|
82
|
+
maxTokens: 131_072,
|
|
83
|
+
input: ['text', 'image'],
|
|
84
|
+
reasoning: true,
|
|
85
|
+
thinkingLevelMap: { low: 'low', medium: 'medium', high: 'high' },
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'qwen3.7-max',
|
|
89
|
+
name: 'Qwen3.7 Max',
|
|
90
|
+
contextWindow: 1_000_000,
|
|
91
|
+
maxTokens: 131_072,
|
|
92
|
+
input: ['text'],
|
|
93
|
+
reasoning: true,
|
|
94
|
+
thinkingLevelMap: { low: 'low', medium: 'medium', high: 'high' },
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'qwen3.7-plus',
|
|
98
|
+
name: 'Qwen3.7 Plus',
|
|
99
|
+
contextWindow: 1_000_000,
|
|
100
|
+
maxTokens: 65_536,
|
|
101
|
+
input: ['text', 'image'],
|
|
102
|
+
reasoning: true,
|
|
103
|
+
thinkingLevelMap: { low: 'low', medium: 'medium', high: 'high' },
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'qwen3.6-plus',
|
|
107
|
+
name: 'Qwen3.6 Plus',
|
|
108
|
+
contextWindow: 1_000_000,
|
|
109
|
+
maxTokens: 65_536,
|
|
110
|
+
input: ['text', 'image'],
|
|
111
|
+
reasoning: true,
|
|
112
|
+
thinkingLevelMap: { low: 'low', medium: 'medium', high: 'high' },
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: 'qwen3.6-flash',
|
|
116
|
+
name: 'Qwen3.6 Flash',
|
|
117
|
+
contextWindow: 1_000_000,
|
|
118
|
+
maxTokens: 65_536,
|
|
119
|
+
input: ['text', 'image'],
|
|
120
|
+
reasoning: true,
|
|
121
|
+
thinkingLevelMap: { low: 'low', medium: 'medium', high: 'high' },
|
|
122
|
+
},
|
|
123
|
+
])
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aliyun DashScope (Bailian) as a DeepSeek Harness model provider.
|
|
3
|
+
*
|
|
4
|
+
* This plugin registers one provider route, `aliyun`, on the harness LLM seam.
|
|
5
|
+
* It owns the route outright — endpoint, credential reference, protocol, and
|
|
6
|
+
* the Qwen model catalog in {@link ./catalog.js} — so a profile needs no model
|
|
7
|
+
* list, and `pnpm update` is how the catalog moves forward.
|
|
8
|
+
*
|
|
9
|
+
* @module @doitian/dsh-provider-aliyun
|
|
10
|
+
*/
|
|
11
|
+
import { LlmError, resolveImageAttachmentAccess } from '@deepseek-ai/dsh-llm'
|
|
12
|
+
import z from '@deepseek-ai/schemastery'
|
|
13
|
+
|
|
14
|
+
import { createAliyunAdapter } from './adapter.js'
|
|
15
|
+
import {
|
|
16
|
+
ALIYUN_MODELS,
|
|
17
|
+
DEFAULT_API_KEY_ENV,
|
|
18
|
+
DEFAULT_BASE_URL,
|
|
19
|
+
DISPLAY_NAME,
|
|
20
|
+
ROUTE,
|
|
21
|
+
} from './catalog.js'
|
|
22
|
+
|
|
23
|
+
/** Entry name, used for logging and as the settings namespace fallback. */
|
|
24
|
+
export const name = 'llm-aliyun'
|
|
25
|
+
|
|
26
|
+
/** This plugin registers routes; it needs the LLM seam and nothing else. */
|
|
27
|
+
export const inject = ['llm']
|
|
28
|
+
|
|
29
|
+
/** Every thinking level a model's `thinkingLevelMap` may name. */
|
|
30
|
+
const THINKING_LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']
|
|
31
|
+
|
|
32
|
+
/** Accepted input modalities. */
|
|
33
|
+
const MODALITIES = ['text', 'image']
|
|
34
|
+
|
|
35
|
+
/** One catalog entry, as a profile may override it. */
|
|
36
|
+
const modelSchema = z.object({
|
|
37
|
+
id: z.string().required(),
|
|
38
|
+
name: z.string(),
|
|
39
|
+
contextWindow: z.number().step(1).min(1),
|
|
40
|
+
maxTokens: z.number().step(1).min(1),
|
|
41
|
+
input: z.array(z.union(MODALITIES)),
|
|
42
|
+
reasoning: z.boolean(),
|
|
43
|
+
thinkingLevelMap: z.dict(z.union([z.string(), z.const(null)])),
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Plugin configuration.
|
|
48
|
+
*
|
|
49
|
+
* Every field defaults to something serviceable, so the bundle's patch mounts
|
|
50
|
+
* this plugin with an empty config and the route works as soon as a key
|
|
51
|
+
* resolves. `models` defaults to the shipped catalog, which is what the
|
|
52
|
+
* settings surface shows as inherited rows until someone edits them.
|
|
53
|
+
*/
|
|
54
|
+
export const Config = z.object({
|
|
55
|
+
displayName: z.string().default(DISPLAY_NAME),
|
|
56
|
+
apiKeyEnv: z.string().role('credential-ref').default(DEFAULT_API_KEY_ENV),
|
|
57
|
+
baseURL: z.string().default(DEFAULT_BASE_URL),
|
|
58
|
+
models: z.array(modelSchema).default(ALIYUN_MODELS),
|
|
59
|
+
reasoning: z.union(THINKING_LEVELS),
|
|
60
|
+
headers: z.dict(z.string()),
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve this route's credential reference for one request.
|
|
65
|
+
*
|
|
66
|
+
* The reference is resolved through the harness credential seam, which reads
|
|
67
|
+
* the managed credential document and the launching environment. A reference
|
|
68
|
+
* that resolves to nothing fails the request before any network I/O, naming
|
|
69
|
+
* the reference to store — the alternative, sending no key, turns a
|
|
70
|
+
* configuration mistake into an opaque upstream 401.
|
|
71
|
+
*
|
|
72
|
+
* @param {import('@deepseek-ai/cordis').Context} ctx - the plugin context.
|
|
73
|
+
* @returns {(provider: string, profile: object) => Promise<string | undefined>} the resolver.
|
|
74
|
+
*/
|
|
75
|
+
function apiKeyResolver(ctx) {
|
|
76
|
+
return async (provider, profile) => {
|
|
77
|
+
const ref = profile.apiKeyEnv
|
|
78
|
+
if (ref === undefined) return undefined
|
|
79
|
+
const credentials = ctx.get('credentials')
|
|
80
|
+
const hit = credentials === undefined ? undefined : (await credentials.resolve(ref))?.value
|
|
81
|
+
if (hit !== undefined && hit.length > 0) return hit
|
|
82
|
+
throw new LlmError(
|
|
83
|
+
`llm-aliyun: no credential for provider route "${provider}"; its profile resolves ${ref}, which is not set — store ${ref} through the credentials service (the Web Models page writes it) or export it in the launching environment`,
|
|
84
|
+
'MISSING_CREDENTIAL',
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Mount the route.
|
|
91
|
+
*
|
|
92
|
+
* @param {import('@deepseek-ai/cordis').Context} ctx - the plugin context.
|
|
93
|
+
* @param {object} config - the resolved {@link Config}.
|
|
94
|
+
*/
|
|
95
|
+
export function apply(ctx, config) {
|
|
96
|
+
// The settings namespace is the entry id, so the Models page addresses this
|
|
97
|
+
// plugin's own row rather than any other adapter's section.
|
|
98
|
+
const settingsNs = ctx.fiber?.entry?.options.id ?? name
|
|
99
|
+
|
|
100
|
+
const adapter = createAliyunAdapter({
|
|
101
|
+
displayName: config.displayName,
|
|
102
|
+
apiKeyEnv: config.apiKeyEnv,
|
|
103
|
+
baseURL: config.baseURL,
|
|
104
|
+
models: config.models,
|
|
105
|
+
reasoning: config.reasoning,
|
|
106
|
+
headers: config.headers,
|
|
107
|
+
resolveApiKey: apiKeyResolver(ctx),
|
|
108
|
+
resolveAttachments: () => ctx.get('attachments'),
|
|
109
|
+
resolveImageAccess: (attachments, ref) => resolveImageAttachmentAccess(
|
|
110
|
+
attachments,
|
|
111
|
+
(hostPath) => ctx.get('fs')?.processPathFromHostPath(hostPath),
|
|
112
|
+
ref,
|
|
113
|
+
),
|
|
114
|
+
onReplayDegrade: ({ provider, model, reason }) => {
|
|
115
|
+
ctx.logger?.warn?.(`llm-aliyun: unusable replay state on assistant history for route "${provider}/${model}"; sending that message as provider-neutral content (${reason})`)
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// Both registrations are disposed with the plugin's fiber.
|
|
120
|
+
ctx.llm.registerAdapter([ROUTE], adapter)
|
|
121
|
+
|
|
122
|
+
// The directory entry is what gives the route a row on the Models page, and
|
|
123
|
+
// with it the API-key field. It is refused if another adapter already
|
|
124
|
+
// declares `aliyun` — which is what happens when a profile still configures
|
|
125
|
+
// an `aliyun` route through `llm-pi-ai`; remove that route to use this plugin.
|
|
126
|
+
ctx.llm.registerConfigurableProviders([{
|
|
127
|
+
provider: ROUTE,
|
|
128
|
+
displayName: config.displayName,
|
|
129
|
+
settingsNs,
|
|
130
|
+
settingsPath: [],
|
|
131
|
+
declared: true,
|
|
132
|
+
}])
|
|
133
|
+
}
|
package/lib/models.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn shipped catalog entries into the pi-ai model descriptors the adapter
|
|
3
|
+
* dispatches with.
|
|
4
|
+
*
|
|
5
|
+
* A catalog entry states only model facts; everything deployment-specific —
|
|
6
|
+
* the route it belongs to, the endpoint, the protocol, the compatibility
|
|
7
|
+
* switches — is supplied here from configuration. That split is what lets the
|
|
8
|
+
* catalog stay a plain data file that a script or a human can update without
|
|
9
|
+
* knowing anything about pi-ai.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Aliyun bills per token; the harness's cost surfaces are not fed from here. */
|
|
13
|
+
const NO_COST = Object.freeze({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 })
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Materialize one route's models.
|
|
17
|
+
*
|
|
18
|
+
* @param {object} input - catalog and route facts.
|
|
19
|
+
* @param {readonly import('./catalog.js').AliyunModel[]} input.catalog - shipped entries.
|
|
20
|
+
* @param {string} input.provider - route name every model is stamped with.
|
|
21
|
+
* @param {string} input.baseUrl - endpoint every model is stamped with.
|
|
22
|
+
* @param {Record<string, unknown>} input.compat - compatibility switches to copy onto each model.
|
|
23
|
+
* @returns {object[]} pi-ai model descriptors.
|
|
24
|
+
*/
|
|
25
|
+
export function materializeModels({ catalog, provider, baseUrl, compat }) {
|
|
26
|
+
return catalog.map((entry) => {
|
|
27
|
+
const reasoning = entry.reasoning === true
|
|
28
|
+
return {
|
|
29
|
+
id: entry.id,
|
|
30
|
+
name: entry.name,
|
|
31
|
+
api: 'openai-completions',
|
|
32
|
+
provider,
|
|
33
|
+
baseUrl,
|
|
34
|
+
reasoning,
|
|
35
|
+
input: [...entry.input],
|
|
36
|
+
cost: { ...NO_COST },
|
|
37
|
+
contextWindow: entry.contextWindow,
|
|
38
|
+
maxTokens: entry.maxTokens,
|
|
39
|
+
// Only a thinking model carries a level map. pi-ai reads an absent map as
|
|
40
|
+
// "no levels beyond off", and a model declared non-reasoning must not
|
|
41
|
+
// advertise levels a selector would then offer.
|
|
42
|
+
...reasoning && entry.thinkingLevelMap !== undefined
|
|
43
|
+
? { thinkingLevelMap: { ...entry.thinkingLevelMap } }
|
|
44
|
+
: {},
|
|
45
|
+
compat: { ...compat },
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
package/lib/provider.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the pi-ai `Provider` the adapter registers for this route.
|
|
3
|
+
*
|
|
4
|
+
* The shape is pi-ai's own provider interface: an id, an endpoint, an auth
|
|
5
|
+
* descriptor, and the protocol implementation every model on the route
|
|
6
|
+
* dispatches through. It mirrors what `@deepseek-ai/dsh-llm-pi-ai` constructs
|
|
7
|
+
* for a route pi-ai ships no catalog for.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Api-key auth for a route the harness authenticates itself.
|
|
12
|
+
*
|
|
13
|
+
* The harness resolves the route's credential reference before the request
|
|
14
|
+
* enters pi-ai and passes the key as the stream's `apiKey` option, which pi-ai
|
|
15
|
+
* treats as the highest-priority override. This descriptor is therefore only
|
|
16
|
+
* the fallback pi-ai consults when that option is absent — it deliberately
|
|
17
|
+
* reports no key rather than inventing one, so a missing credential stays the
|
|
18
|
+
* harness's loud failure instead of becoming a confusing upstream 401.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} name - label used as the resolution's status name.
|
|
21
|
+
* @returns {object} pi-ai api-key auth.
|
|
22
|
+
*/
|
|
23
|
+
function harnessApiKeyAuth(name) {
|
|
24
|
+
return {
|
|
25
|
+
name,
|
|
26
|
+
resolve: ({ credential }) => Promise.resolve({
|
|
27
|
+
auth: credential?.key === undefined ? {} : { apiKey: credential.key },
|
|
28
|
+
}),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Construct one static, single-protocol provider.
|
|
34
|
+
*
|
|
35
|
+
* @param {object} input - route identity and dispatch.
|
|
36
|
+
* @param {string} input.id - route name.
|
|
37
|
+
* @param {string} input.name - display name.
|
|
38
|
+
* @param {string} input.baseUrl - endpoint every model on the route uses.
|
|
39
|
+
* @param {object[]} input.models - pi-ai model descriptors.
|
|
40
|
+
* @param {object} input.api - pi-ai protocol implementation.
|
|
41
|
+
* @returns {object} the pi-ai provider.
|
|
42
|
+
*/
|
|
43
|
+
export function createPiProvider({ id, name, baseUrl, models, api }) {
|
|
44
|
+
return {
|
|
45
|
+
id,
|
|
46
|
+
name,
|
|
47
|
+
baseUrl,
|
|
48
|
+
auth: { apiKey: harnessApiKeyAuth(name) },
|
|
49
|
+
getModels: () => models,
|
|
50
|
+
stream: (model, context, options) => api.stream(model, context, options),
|
|
51
|
+
streamSimple: (model, context, options) => api.streamSimple(model, context, options),
|
|
52
|
+
}
|
|
53
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@doitian/dsh-provider-aliyun",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Aliyun DashScope (Bailian) model provider for DeepSeek Harness: registers an `aliyun` route carrying the Qwen model catalog, so the model list ships in the package instead of a profile.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dsh",
|
|
7
|
+
"dsh-plugin",
|
|
8
|
+
"dsh-bundle",
|
|
9
|
+
"deepseek-harness",
|
|
10
|
+
"aliyun",
|
|
11
|
+
"aliyuncs",
|
|
12
|
+
"dashscope",
|
|
13
|
+
"bailian",
|
|
14
|
+
"qwen",
|
|
15
|
+
"llm",
|
|
16
|
+
"provider"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "doitian",
|
|
20
|
+
"homepage": "https://github.com/doitian/dsh-provider-aliyun#readme",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/doitian/dsh-provider-aliyun.git"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/doitian/dsh-provider-aliyun/issues"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "lib/index.js",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./lib/index.js",
|
|
32
|
+
"./catalog": "./lib/catalog.js",
|
|
33
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"lib/index.js",
|
|
38
|
+
"lib/adapter.js",
|
|
39
|
+
"lib/catalog.js",
|
|
40
|
+
"lib/models.js",
|
|
41
|
+
"lib/provider.js",
|
|
42
|
+
"cordis.patch.yml"
|
|
43
|
+
],
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=22"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"validate": "node scripts/validate.mjs",
|
|
52
|
+
"test": "node --test",
|
|
53
|
+
"prepack": "npm run validate && npm run test"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@earendil-works/pi-ai": "^0.87.1"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@deepseek-ai/cordis": "~4.0.4",
|
|
60
|
+
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
61
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.2.0-rc.2",
|
|
62
|
+
"@deepseek-ai/schemastery": "~3.18.4"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@deepseek-ai/cordis": "4.0.4",
|
|
66
|
+
"@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
|
|
67
|
+
"@deepseek-ai/dsh-authorization": "0.2.0-rc.2",
|
|
68
|
+
"@deepseek-ai/dsh-brand": "0.2.0-rc.2",
|
|
69
|
+
"@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
|
|
70
|
+
"@deepseek-ai/dsh-fs": "0.2.0-rc.2",
|
|
71
|
+
"@deepseek-ai/dsh-launch-environment": "0.2.0-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
73
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.2.0-rc.2",
|
|
74
|
+
"@deepseek-ai/dsh-timeout": "0.2.0-rc.2",
|
|
75
|
+
"@deepseek-ai/dsh-util-values": "0.2.0-rc.2",
|
|
76
|
+
"@deepseek-ai/schemastery": "3.18.4",
|
|
77
|
+
"@earendil-works/pi-ai": "0.87.1",
|
|
78
|
+
"js-yaml": "^4.1.0"
|
|
79
|
+
},
|
|
80
|
+
"dsh": {
|
|
81
|
+
"bundle": {
|
|
82
|
+
"patch": "./cordis.patch.yml"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|