dsh-custom-api 0.0.0-stage → 1.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 6zev
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 CHANGED
@@ -1,3 +1,96 @@
1
- # Temporary Holding Version
1
+ # DSH Custom API — Model Discovery & Capability Enhancer
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <p align="center">
4
+ <img src="./icon.svg" width="80" height="80" alt="DSH Custom API Logo" />
5
+ </p>
6
+
7
+ Plugin mở rộng cho **DeepSeek Harness (DSH)**, kết hợp **API Endpoint Metadata**, cơ sở dữ liệu mở **[Models.dev](https://models.dev/)** và bộ quy chuẩn **Vendor Reasoning Ladders** (tham khảo từ [dsh-dc-ai-api](https://github.com/dabeecao/dsh-dc-ai-api)) để tự động khám phá và cấu hình hoàn chỉnh các model từ Custom API:
8
+ - 🖼️ **Vision (Multimodal Image Input)**: Nhận diện model đa phương thức, tự gán `input: ["text", "image"]`.
9
+ - 🧠 **Thinking / Reasoning Effort**: Gán chuẩn thang đo suy nghĩ của từng hãng (`none/low/high/max` cho DeepSeek, 5 mức cho Claude & OpenAI, Flash ladder cho Gemini, v.v.).
10
+ - 🛠️ **Tool Calling**: Đảm bảo tương thích hoàn toàn cho việc gọi Function / Tools của Agent.
11
+ - 📏 **Context Window & Max Tokens**: Trích xuất kích thước ngữ cảnh và kẹp giới hạn token chuẩn xác.
12
+
13
+ ---
14
+
15
+ ## 🚀 Cài Đặt
16
+
17
+ ### 1. Cài đặt qua npm registry
18
+ ```bash
19
+ dsh plugin --profile web add dsh-custom-api
20
+ ```
21
+
22
+ ### 2. Cài đặt trực tiếp từ GitHub
23
+ ```bash
24
+ dsh plugin --profile web add github:6zev/dsh-custom-api
25
+ ```
26
+
27
+ ---
28
+
29
+ ## 🌟 Tính Năng Nổi Bật
30
+
31
+ ### 1. Phân Tích Khả Năng Model 3 Tầng (Không Đoán Mò)
32
+ 1. **Tier 1 - Trực tiếp từ API Endpoint Metadata**:
33
+ - Trích xuất các trường `architecture.modality`, `modalities.input`, `supported_parameters`, `capabilities`, `features`, `model_info`.
34
+ 2. **Tier 2 - Tra cứu [Models.dev](https://models.dev/) Database**:
35
+ - Tự động cache và đối soát hơn 4.800+ models từ cơ sở dữ liệu mở quốc tế [Models.dev](https://models.dev/).
36
+ - Trích xuất chính xác giới hạn context, output tokens, cờ vision và reasoning.
37
+ 3. **Tier 3 - Heuristic Fallback**:
38
+ - Dự phòng thông minh cho các model nội bộ mới ra mắt.
39
+
40
+ ### 2. Thang Đo Reasoning Chuẩn Theo Từng Hãng (Vendor Ladders)
41
+ | Hãng (Family) | Thang đo Thinking Effort | Đặc điểm |
42
+ | :--- | :--- | :--- |
43
+ | **DeepSeek** | `none` / `low` / `high` / `max` | `thinkingFormat: "deepseek"` |
44
+ | **OpenAI (GPT / o-series)** | `low` / `medium` / `high` / `xhigh` / `max` | Chuẩn 5 mức OpenAI |
45
+ | **Google Gemini** | Theo từng model (`GEMINI_LEVELS`): `minimal`, `low`, `medium`, `high` | Không có `max` |
46
+ | **Anthropic Claude** | `low` / `medium` / `high` / `xhigh` / `max` | Chuẩn Claude 3.7+ |
47
+ | **xAI Grok** | `low` / `medium` / `high` / `xhigh` | Không có `off` |
48
+ | **Zhipu GLM** | `none` / `high` / `max` | Chuẩn GLM-5.2+ |
49
+ | **Qwen & Khác** | Toggle only (`off: null`, `high: high`) | Bật/tắt suy nghĩ |
50
+ | **Model thường** | `reasoningEfforts: false` | Tắt hoàn toàn thinking |
51
+
52
+ ### 3. Lọc Bỏ Endpoint Thuần Sinh Ảnh
53
+ - Tự động loại bỏ các endpoint như `gpt-image-*`, `grok-imagine-*`, `flux`, `midjourney` khỏi Chat Catalog để tránh lỗi giao thức.
54
+
55
+ ---
56
+
57
+ ## 📖 Cách Sử Dụng
58
+
59
+ ### 1. Dùng Slash Command trong chat
60
+ - Quét API và cấu hình ngay vào DSH:
61
+ ```text
62
+ /discover-models https://api.your-relay.com/v1 sk-xxxx my-provider
63
+ ```
64
+ - Nâng cấp toàn bộ model hiện có trong DSH:
65
+ ```text
66
+ /enhance-models
67
+ ```
68
+
69
+ ### 2. Dùng Web UI Settings
70
+ 1. Vào **Settings -> Models -> Custom model API**.
71
+ 2. Nhập URL và API Key, bấm **Fetch models**. Plugin sẽ can thiệp để danh sách model trả về đã nhận diện sẵn Vision và Thinking.
72
+
73
+ ### 3. Giao việc cho AI Agent
74
+ Bạn chỉ cần nhắn cho AI:
75
+ > *"Quét endpoint https://dc-ai.dabeecao.org/v1 với API key ... và lưu vào DSH giúp tôi"*
76
+
77
+ ---
78
+
79
+ ## 🔐 Cấu Hình Trusted Publisher (npm)
80
+
81
+ Workflow GitHub Actions (`.github/workflows/publish.yml`) đã được tích hợp sẵn quyền OIDC `id-token: write` và cờ `--provenance`.
82
+
83
+ Để liên kết Trusted Publisher trên npm:
84
+ 1. Truy cập: [https://www.npmjs.com/package/dsh-custom-api/access](https://www.npmjs.com/package/dsh-custom-api/access)
85
+ 2. Trong phần **Publishing Access** $\rightarrow$ **Trusted Publishers**, chọn **Add Trusted Publisher** $\rightarrow$ **GitHub Actions**.
86
+ 3. Điền:
87
+ - **Repository Owner**: `6zev`
88
+ - **Repository Name**: `dsh-custom-api`
89
+ - **Workflow filename**: `publish.yml`
90
+ 4. Từ các lần phát hành tiếp theo, GitHub Actions sẽ tự động xác thực và ký số Provenance mà không cần secret token tĩnh.
91
+
92
+ ---
93
+
94
+ ## 📄 Giấy Phép
95
+
96
+ MIT License © 2026 [6zev](https://github.com/6zev)
@@ -0,0 +1,4 @@
1
+ - insert:
2
+ - id: custom-model-discovery
3
+ name: dsh-custom-api
4
+ config: {}
package/icon.svg ADDED
@@ -0,0 +1,15 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="48" height="48" fill="none">
2
+ <defs>
3
+ <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" stop-color="#4f46e5" />
5
+ <stop offset="100%" stop-color="#06b6d4" />
6
+ </linearGradient>
7
+ </defs>
8
+ <rect width="24" height="24" rx="6" fill="url(#grad1)" />
9
+ <!-- Sparkle / Discovery icon -->
10
+ <path d="M12 4L13.5 9.5L19 11L13.5 12.5L12 18L10.5 12.5L5 11L10.5 9.5L12 4Z" fill="#ffffff" />
11
+ <!-- Eye / Vision hint -->
12
+ <circle cx="17.5" cy="6.5" r="1.5" fill="#fef08a" />
13
+ <!-- Tool / Brain node -->
14
+ <circle cx="6.5" cy="17.5" r="1.5" fill="#a7f3d0" />
15
+ </svg>
package/lib/index.js ADDED
@@ -0,0 +1,934 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ /**
5
+ * Plugin: @local/dsh-model-discovery
6
+ * Tự động phát hiện model từ Custom API (OpenAI/Anthropic compatible),
7
+ * kết hợp API Endpoint Metadata, models.dev và bộ quy chuẩn Reasoning Ladder theo từng hãng (vendor)
8
+ * lấy cảm hứng từ dsh-dc-ai-api.
9
+ */
10
+
11
+ export const name = 'dsh-custom-api';
12
+ export const inject = ['llm', 'tools', 'commands', 'configEditor'];
13
+
14
+ const CACHE_PATH = '/root/.dsh/storages/models-dev-cache.json';
15
+ const MODELS_DEV_URL = 'https://models.dev/api.json';
16
+ const MODELS_DEV_TTL_MS = 6 * 60 * 60 * 1000; // 6 hours
17
+
18
+ export const DEFAULT_CONTEXT_WINDOW = 262144;
19
+ export const DEFAULT_MAX_TOKENS = 32768;
20
+
21
+ /**
22
+ * Các provider chính thống được ưu tiên lấy metadata chuẩn nhất trong models.dev
23
+ */
24
+ const PREFERRED_PROVIDERS = new Set([
25
+ 'openai',
26
+ 'anthropic',
27
+ 'google',
28
+ 'deepseek',
29
+ 'xai',
30
+ 'zhipuai',
31
+ 'z-ai',
32
+ 'alibaba',
33
+ 'moonshotai',
34
+ 'minimax',
35
+ 'mistral',
36
+ ]);
37
+
38
+ /** Model id chỉ sinh ảnh (image generation), không phải chat model */
39
+ const IMAGE_MODEL_ID = /(^|[-_.])(image|imagine)([-_.]|$)/i;
40
+
41
+ /** Cụm từ mô tả chắc chắn hỗ trợ Vision */
42
+ const VISION_DESCRIPTION =
43
+ /\b(vision|visual|multimodal|image input|input images?|accepts? images?|image understanding|image reasoning|text and image)\b/i;
44
+
45
+ /** Cụm từ mô tả chắc chắn là text-only (bản finetune bỏ vision) */
46
+ const TEXT_ONLY_DESCRIPTION = /\btext[- ]only\b|\btext input\b/i;
47
+
48
+ /** Model ID và từ khóa biểu thị Thinking / Reasoning */
49
+ const THINKING_MODEL_ID = /(^|[-_.])(thinking|reasoner|r1|o1|o3|o4|qwq|qvq)([-_.]|$)/i;
50
+ const THINKING_DESCRIPTION =
51
+ /\b(thinking|reasoning|reasoner|thought|chain[- ]of[- ]thought|deep thinking)\b/i;
52
+
53
+ /**
54
+ * Reasoning Ladder chuẩn hóa theo từng hãng từ tài liệu API chính thức:
55
+ * - DeepSeek: none / low / high / max (api-docs.deepseek.com)
56
+ * - OpenAI: low / medium / high / xhigh / max (platform.openai.com)
57
+ * - Gemini: per-model (low/medium/high, Flash dòng 3.x có thêm minimal)
58
+ * - Claude: low / medium / high / xhigh / max (platform.claude.com)
59
+ * - Grok: low / medium / high / xhigh (docs.x.ai)
60
+ * - GLM: none / high / max (docs.z.ai)
61
+ * - Qwen: toggle only (off: null, high: high)
62
+ */
63
+ const FAMILY_EFFORTS = Object.freeze({
64
+ deepseek: { off: 'none', low: 'low', high: 'high', max: 'max' },
65
+ openai: { low: 'low', medium: 'medium', high: 'high', xhigh: 'xhigh', max: 'max' },
66
+ gemini: { low: 'low', medium: 'medium', high: 'high' },
67
+ claude: { low: 'low', medium: 'medium', high: 'high', xhigh: 'xhigh', max: 'max' },
68
+ grok: { low: 'low', medium: 'medium', high: 'high', xhigh: 'xhigh' },
69
+ glm: { off: 'none', high: 'high', max: 'max' },
70
+ qwen: { off: null, high: 'high' },
71
+ other: { off: null, high: 'high' },
72
+ });
73
+
74
+ const GEMINI_LEVELS = Object.freeze({
75
+ 'gemini-4-argon': ['low', 'medium', 'high'],
76
+ 'gemini-3.8-flash': ['low', 'medium', 'high'],
77
+ 'gemini-3.6-flash': ['minimal', 'low', 'medium', 'high'],
78
+ 'gemini-3.5-flash-lite': ['minimal', 'low', 'medium', 'high'],
79
+ 'gemini-3.1-pro-preview': ['low', 'medium', 'high'],
80
+ 'gemini-3.1-flash-lite-image': ['minimal', 'high'],
81
+ 'gemini-3-flash-preview': ['minimal', 'low', 'medium', 'high'],
82
+ 'gemini-3-pro-preview': ['low', 'high'],
83
+ 'gemini-2.5-pro': ['low', 'medium', 'high'],
84
+ 'gemini-2.5-flash': ['low', 'medium', 'high'],
85
+ 'gemini-2.5-flash-lite': ['low', 'medium', 'high'],
86
+ 'gemini-2.0-flash-thinking': ['low', 'medium', 'high'],
87
+ });
88
+
89
+ function geminiEfforts(id) {
90
+ const norm = String(id).toLowerCase();
91
+ const documented = GEMINI_LEVELS[norm] || ['low', 'medium', 'high'];
92
+ const efforts = {};
93
+ for (const level of documented) efforts[level] = level;
94
+ return efforts;
95
+ }
96
+
97
+ export function normalizeModelId(id) {
98
+ const tail = String(id).toLowerCase().split('/').pop() || '';
99
+ return tail
100
+ .replace(/[._]/g, '-')
101
+ .replace(/[^a-z0-9-]/g, '')
102
+ .replace(/-+/g, '-')
103
+ .replace(/^-|-$/g, '');
104
+ }
105
+
106
+ // In-memory models.dev index
107
+ let exactIndex = new Map();
108
+ let normalizedIndex = new Map();
109
+ let rawModelsDevData = null;
110
+ let isLoaded = false;
111
+
112
+ function pickBest(candidates) {
113
+ let best = undefined;
114
+ let bestScore = -1;
115
+ for (const candidate of candidates) {
116
+ let score = 0;
117
+ if (Array.isArray(candidate.modalities?.input) && candidate.modalities.input.length > 0) score += 4;
118
+ if (Number.isFinite(candidate.limit?.context) && candidate.limit.context > 0) score += 2;
119
+ if (Number.isFinite(candidate.limit?.output) && candidate.limit.output > 0) score += 1;
120
+ if (typeof candidate.reasoning === 'boolean') score += 1;
121
+ if (PREFERRED_PROVIDERS.has(candidate.provider)) score += 1;
122
+ if (score > bestScore) {
123
+ bestScore = score;
124
+ best = candidate;
125
+ }
126
+ }
127
+ return best;
128
+ }
129
+
130
+ function buildModelsDevIndex(data) {
131
+ rawModelsDevData = data;
132
+ exactIndex = new Map();
133
+ normalizedIndex = new Map();
134
+
135
+ for (const [providerId, provider] of Object.entries(data)) {
136
+ if (!provider || typeof provider.models !== 'object') continue;
137
+ for (const [mId, mData] of Object.entries(provider.models || {})) {
138
+ if (!mData || typeof mData !== 'object') continue;
139
+ const record = { ...mData, provider: providerId, modelId: mId };
140
+
141
+ if (!exactIndex.has(mId)) exactIndex.set(mId, []);
142
+ exactIndex.get(mId).push(record);
143
+
144
+ const norm = normalizeModelId(mId);
145
+ if (!normalizedIndex.has(norm)) normalizedIndex.set(norm, []);
146
+ normalizedIndex.get(norm).push(record);
147
+ }
148
+ }
149
+ isLoaded = true;
150
+ }
151
+
152
+ async function initModelsDevDatabase(logger) {
153
+ try {
154
+ if (fs.existsSync(CACHE_PATH)) {
155
+ const content = fs.readFileSync(CACHE_PATH, 'utf8');
156
+ const data = JSON.parse(content);
157
+ buildModelsDevIndex(data);
158
+ logger?.info(`models.dev index loaded from cache: ${exactIndex.size} model keys`);
159
+ }
160
+ } catch (err) {
161
+ logger?.warn(`Failed to read models.dev cache: ${err.message}`);
162
+ }
163
+
164
+ // Tự động kiểm tra làm mới cache nền
165
+ try {
166
+ let shouldFetch = !fs.existsSync(CACHE_PATH);
167
+ if (!shouldFetch) {
168
+ const stat = fs.statSync(CACHE_PATH);
169
+ if (Date.now() - stat.mtimeMs > MODELS_DEV_TTL_MS) shouldFetch = true;
170
+ }
171
+
172
+ if (shouldFetch) {
173
+ fetch(MODELS_DEV_URL, { signal: AbortSignal.timeout(15000) })
174
+ .then((res) => {
175
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
176
+ return res.json();
177
+ })
178
+ .then((data) => {
179
+ fs.mkdirSync(path.dirname(CACHE_PATH), { recursive: true });
180
+ fs.writeFileSync(CACHE_PATH, JSON.stringify(data), 'utf8');
181
+ buildModelsDevIndex(data);
182
+ logger?.info('models.dev database refreshed from online registry');
183
+ })
184
+ .catch((err) => {
185
+ logger?.warn(`models.dev background refresh skipped: ${err.message}`);
186
+ });
187
+ }
188
+ } catch (err) {
189
+ logger?.warn(`models.dev init error: ${err.message}`);
190
+ }
191
+ }
192
+
193
+ function lookupModelsDev(id) {
194
+ if (!isLoaded || !id) return null;
195
+ const exact = exactIndex.get(id);
196
+ if (exact && exact.length > 0) return { ...pickBest(exact), matchType: 'exact' };
197
+
198
+ const norm = normalizeModelId(id);
199
+ const normalized = normalizedIndex.get(norm);
200
+ if (normalized && normalized.length > 0) return { ...pickBest(normalized), matchType: 'normalized' };
201
+
202
+ // Fuzzy Token Overlap
203
+ if (rawModelsDevData) {
204
+ const tokens = id.toLowerCase().split(/[-_./:]/).filter((t) => t.length >= 3);
205
+ if (tokens.length >= 1) {
206
+ for (const [pId, p] of Object.entries(rawModelsDevData)) {
207
+ for (const [mId, mData] of Object.entries(p.models || {})) {
208
+ const ms = mId.toLowerCase();
209
+ const matchedCount = tokens.filter((t) => ms.includes(t)).length;
210
+ if (matchedCount >= 2 || (tokens.length === 1 && ms.includes(tokens[0]))) {
211
+ return {
212
+ ...mData,
213
+ modelId: mId,
214
+ provider: pId,
215
+ matchType: 'fuzzy',
216
+ matchedFrom: mId,
217
+ };
218
+ }
219
+ }
220
+ }
221
+ }
222
+ }
223
+
224
+ return null;
225
+ }
226
+
227
+ /**
228
+ * Trích xuất trực tiếp capabilities từ API Endpoint Metadata
229
+ */
230
+ function extractFromApiMetadata(raw) {
231
+ if (!raw || typeof raw !== 'object') return {};
232
+ const result = {};
233
+
234
+ if (
235
+ raw.modalities?.input?.includes('image') ||
236
+ raw.input_modalities?.includes('image') ||
237
+ (typeof raw.architecture?.modality === 'string' && raw.architecture.modality.includes('image')) ||
238
+ raw.supports_vision === true ||
239
+ raw.capabilities?.vision === true ||
240
+ raw.features?.includes('vision') ||
241
+ raw.model_info?.supports_vision === true ||
242
+ raw.attachment === true
243
+ ) {
244
+ result.vision = true;
245
+ }
246
+
247
+ if (
248
+ raw.tool_call === true ||
249
+ raw.supports_function_calling === true ||
250
+ raw.capabilities?.tools === true ||
251
+ raw.features?.includes('tools') ||
252
+ raw.supported_parameters?.includes('tools') ||
253
+ raw.supported_parameters?.includes('tool_choice')
254
+ ) {
255
+ result.toolCall = true;
256
+ }
257
+
258
+ if (
259
+ raw.supports_thinking === true ||
260
+ raw.reasoning === true ||
261
+ (Array.isArray(raw.reasoning_options) && raw.reasoning_options.length > 0) ||
262
+ raw.capabilities?.reasoning === true ||
263
+ raw.features?.includes('reasoning') ||
264
+ raw.features?.includes('thinking') ||
265
+ raw.supported_parameters?.includes('reasoning') ||
266
+ raw.supported_parameters?.includes('include_reasoning') ||
267
+ raw.supported_parameters?.includes('reasoning_effort')
268
+ ) {
269
+ result.reasoning = true;
270
+ }
271
+
272
+ const context =
273
+ raw.context_length ||
274
+ raw.context_window ||
275
+ raw.contextWindow ||
276
+ raw.max_input_tokens ||
277
+ raw.max_model_len ||
278
+ raw.limit?.context;
279
+
280
+ if (typeof context === 'number' && Number.isFinite(context) && context > 0) {
281
+ result.contextWindow = context;
282
+ }
283
+
284
+ const output =
285
+ raw.max_output_tokens ||
286
+ raw.max_completion_tokens ||
287
+ raw.maxTokens ||
288
+ raw.max_tokens ||
289
+ raw.limit?.output ||
290
+ raw.top_provider?.max_completion_tokens;
291
+
292
+ if (typeof output === 'number' && Number.isFinite(output) && output > 0) {
293
+ result.maxTokens = output;
294
+ }
295
+
296
+ return result;
297
+ }
298
+
299
+ /**
300
+ * Nhận diện Family của model (theo chuẩn dc-ai-api)
301
+ */
302
+ function identifyModelFamily(id, dev) {
303
+ const s = String(id).toLowerCase();
304
+ const provider = String(dev?.provider || '').toLowerCase();
305
+
306
+ if (s.includes('deepseek') || s.startsWith('r1') || s.includes('-r1') || provider === 'deepseek') {
307
+ return 'deepseek';
308
+ }
309
+ if (
310
+ s.startsWith('gpt-') ||
311
+ s.includes('-gpt-') ||
312
+ s.startsWith('o1') ||
313
+ s.startsWith('o3') ||
314
+ s.startsWith('o4') ||
315
+ provider === 'openai'
316
+ ) {
317
+ return 'openai';
318
+ }
319
+ if (s.includes('gemini') || provider === 'google') return 'gemini';
320
+ if (s.includes('claude') || provider === 'anthropic') return 'claude';
321
+ if (s.includes('grok') || provider === 'xai') return 'grok';
322
+ if (s.includes('glm') || provider === 'zhipuai' || provider === 'z-ai') return 'glm';
323
+ if (s.includes('qwen') || provider === 'alibaba') return 'qwen';
324
+
325
+ return 'other';
326
+ }
327
+
328
+ /**
329
+ * Xác định Reasoning Efforts chuẩn hóa
330
+ */
331
+ function determineReasoningEfforts(id, model, dev, forceThinking) {
332
+ let isThinking = forceThinking;
333
+ if (!isThinking) {
334
+ if (typeof model?.supports_thinking === 'boolean') isThinking = model.supports_thinking;
335
+ else if (dev?.reasoning === true) isThinking = true;
336
+ else if (THINKING_MODEL_ID.test(String(id))) isThinking = true;
337
+ else {
338
+ const desc = typeof model?.description === 'string' ? model.description : '';
339
+ isThinking = THINKING_DESCRIPTION.test(desc);
340
+ }
341
+ }
342
+
343
+ if (!isThinking) return false;
344
+
345
+ const family = identifyModelFamily(id, dev);
346
+ if (family === 'gemini') return geminiEfforts(id);
347
+ return { ...FAMILY_EFFORTS[family] };
348
+ }
349
+
350
+ /**
351
+ * Phát hiện Vision (Image Input)
352
+ */
353
+ function determineVision(id, model, dev, forceVision) {
354
+ if (forceVision) return true;
355
+ const desc = typeof model?.description === 'string' ? model.description : '';
356
+ if (TEXT_ONLY_DESCRIPTION.test(desc)) return false;
357
+
358
+ const inputs = dev?.modalities?.input;
359
+ if (Array.isArray(inputs) && inputs.length > 0) {
360
+ return inputs.includes('image');
361
+ }
362
+
363
+ if (dev?.attachment === true) return true;
364
+ if (VISION_DESCRIPTION.test(desc)) return true;
365
+
366
+ // Fallback pattern matching
367
+ const s = String(id).toLowerCase();
368
+ return (
369
+ s.includes('vision') ||
370
+ s.includes('vl') ||
371
+ s.includes('4o') ||
372
+ s.includes('4-turbo') ||
373
+ s.includes('gemini') ||
374
+ s.includes('claude') ||
375
+ s.includes('pixtral') ||
376
+ s.includes('llava') ||
377
+ s.includes('omni') ||
378
+ s.includes('internvl') ||
379
+ s.includes('multimodal')
380
+ );
381
+ }
382
+
383
+ /**
384
+ * Kiểm tra model có phải thuần Image generation không (không chat được)
385
+ */
386
+ function isImageGenerationEndpoint(id, dev) {
387
+ if (IMAGE_MODEL_ID.test(String(id))) return true;
388
+ const output = dev?.modalities?.output;
389
+ return Array.isArray(output) && output.length > 0 && output.every((m) => m !== 'text');
390
+ }
391
+
392
+ /**
393
+ * Giải quyết và gán năng lực hoàn chỉnh cho một model
394
+ */
395
+ function resolveCapabilities(rawModel, opts = {}) {
396
+ const id = typeof rawModel === 'string' ? rawModel : rawModel.id;
397
+ const dev = lookupModelsDev(id);
398
+ const apiMeta = extractFromApiMetadata(rawModel);
399
+
400
+ const isImageEndpoint = isImageGenerationEndpoint(id, dev);
401
+ const hasVision = determineVision(id, rawModel, dev, opts.forceVision);
402
+ const reasoningEfforts = determineReasoningEfforts(id, rawModel, dev, opts.forceThinking);
403
+
404
+ // Tool calls
405
+ const hasToolCall = !isImageEndpoint && (apiMeta.toolCall !== false && dev?.tool_call !== false);
406
+
407
+ // Context window & Output limit
408
+ const contextWindow =
409
+ apiMeta.contextWindow ||
410
+ (typeof rawModel === 'object' && rawModel.contextWindow) ||
411
+ dev?.limit?.context ||
412
+ DEFAULT_CONTEXT_WINDOW;
413
+
414
+ const rawMaxTokens =
415
+ apiMeta.maxTokens ||
416
+ (typeof rawModel === 'object' && rawModel.maxTokens) ||
417
+ dev?.limit?.output ||
418
+ DEFAULT_MAX_TOKENS;
419
+
420
+ // Clamp maxTokens vào contextWindow (tránh lỗi provider từ chối)
421
+ const maxTokens = Math.min(rawMaxTokens, contextWindow);
422
+
423
+ const name =
424
+ (typeof rawModel === 'object' && (rawModel.name || rawModel.display_name)) ||
425
+ dev?.name ||
426
+ id;
427
+
428
+ const modelEntry = {
429
+ id,
430
+ name,
431
+ contextWindow,
432
+ maxTokens,
433
+ input: hasVision ? ['text', 'image'] : ['text'],
434
+ reasoningEfforts: reasoningEfforts ? reasoningEfforts : false,
435
+ };
436
+
437
+ const apiProtocol = opts.api || 'openai-completions';
438
+ if (reasoningEfforts && apiProtocol === 'openai-completions') {
439
+ const s = id.toLowerCase();
440
+ const isDeepSeek = s.includes('deepseek') || s.includes('r1');
441
+ modelEntry.compat = {
442
+ supportsReasoningEffort: true,
443
+ ...(isDeepSeek ? { thinkingFormat: 'deepseek' } : {}),
444
+ };
445
+ }
446
+
447
+ return {
448
+ modelEntry,
449
+ hasVision,
450
+ hasThinking: Boolean(reasoningEfforts),
451
+ hasToolCall,
452
+ isImageEndpoint,
453
+ sources: {
454
+ matchedInModelsDev: dev ? dev.modelId : null,
455
+ modelsDevProvider: dev ? dev.provider : null,
456
+ family: identifyModelFamily(id, dev),
457
+ },
458
+ };
459
+ }
460
+
461
+ async function fetchModelsFromApi(baseURL, apiKey, apiProtocol = 'openai-completions') {
462
+ let cleanUrl = baseURL.trim().replace(/\/+$/, '');
463
+ const urlsToTry = cleanUrl.endsWith('/v1')
464
+ ? [`${cleanUrl}/models`]
465
+ : [`${cleanUrl}/v1/models`, `${cleanUrl}/models`];
466
+
467
+ const headers = { Accept: 'application/json' };
468
+ if (apiKey) {
469
+ if (apiProtocol === 'anthropic-messages') {
470
+ headers['x-api-key'] = apiKey;
471
+ headers['anthropic-version'] = '2023-06-01';
472
+ } else {
473
+ headers['Authorization'] = `Bearer ${apiKey}`;
474
+ }
475
+ }
476
+
477
+ let lastError = null;
478
+ for (const url of urlsToTry) {
479
+ try {
480
+ const res = await fetch(url, { method: 'GET', headers });
481
+ if (res.ok) {
482
+ const data = await res.json();
483
+ let list = [];
484
+ if (Array.isArray(data)) list = data;
485
+ else if (Array.isArray(data.data)) list = data.data;
486
+ else if (data.models && typeof data.models === 'object') {
487
+ list = Array.isArray(data.models) ? data.models : Object.values(data.models);
488
+ }
489
+ if (list.length > 0) return { url, models: list };
490
+ } else {
491
+ lastError = new Error(`HTTP ${res.status} ${res.statusText} from ${url}`);
492
+ }
493
+ } catch (err) {
494
+ lastError = err;
495
+ }
496
+ }
497
+
498
+ throw new Error(`Không thể lấy danh sách model từ ${baseURL}: ${lastError?.message || 'Không tìm thấy models'}`);
499
+ }
500
+
501
+ export function apply(ctx) {
502
+ initModelsDevDatabase(ctx.logger);
503
+ ctx.logger?.info('Custom Model Discovery Plugin (API Metadata + models.dev + Vendor Reasoning Ladders) active');
504
+
505
+ // =========================================================================
506
+ // 1. Live Hook vào ctx.llm.discoverModels (Web UI Settings -> Models)
507
+ // =========================================================================
508
+ if (ctx.llm && typeof ctx.llm.discoverModels === 'function') {
509
+ const originalDiscoverModels = ctx.llm.discoverModels.bind(ctx.llm);
510
+ ctx.llm.discoverModels = async function (settingsNs, request, signal) {
511
+ const discovered = await originalDiscoverModels(settingsNs, request, signal);
512
+ if (!Array.isArray(discovered)) return discovered;
513
+
514
+ return discovered.map((model) => {
515
+ const cap = resolveCapabilities(model);
516
+ return {
517
+ ...model,
518
+ contextWindow: cap.modelEntry.contextWindow,
519
+ maxTokens: cap.modelEntry.maxTokens,
520
+ inputModalities: cap.hasVision ? ['text', 'image'] : ['text'],
521
+ };
522
+ });
523
+ };
524
+ ctx.effect(() => () => {
525
+ ctx.llm.discoverModels = originalDiscoverModels;
526
+ });
527
+ }
528
+
529
+ // =========================================================================
530
+ // 2. Live Hook vào ctx.llm.resolveModelInfo (Chat / Agent Execution)
531
+ // =========================================================================
532
+ if (ctx.llm && typeof ctx.llm.resolveModelInfo === 'function') {
533
+ const originalResolveModelInfo = ctx.llm.resolveModelInfo.bind(ctx.llm);
534
+ ctx.llm.resolveModelInfo = async function (provider, model, signal) {
535
+ const info = await originalResolveModelInfo(provider, model, signal);
536
+ if (!info) return info;
537
+
538
+ const cap = resolveCapabilities(model);
539
+
540
+ if (cap.hasThinking && !info.reasoning) {
541
+ info.reasoning = {
542
+ efforts: Object.keys(cap.modelEntry.reasoningEfforts)
543
+ .filter((lvl) => lvl !== 'off')
544
+ .map((lvl) => ({
545
+ id: lvl,
546
+ name: lvl.charAt(0).toUpperCase() + lvl.slice(1),
547
+ })),
548
+ defaultEffort: 'medium',
549
+ };
550
+ }
551
+
552
+ if (cap.hasVision) {
553
+ const modalities = new Set(info.inputModalities || ['text']);
554
+ modalities.add('image');
555
+ info.inputModalities = Array.from(modalities);
556
+ }
557
+
558
+ return info;
559
+ };
560
+ ctx.effect(() => () => {
561
+ ctx.llm.resolveModelInfo = originalResolveModelInfo;
562
+ });
563
+ }
564
+
565
+ // =========================================================================
566
+ // 3. Hàm lưu cấu hình vào DSH giữ nguyên compat tùy biến (preserveCompat)
567
+ // =========================================================================
568
+ async function updateDshProviderConfig(providerId, providerData) {
569
+ if (!ctx.configEditor) {
570
+ throw new Error('configEditor service is not available');
571
+ }
572
+
573
+ const entries = ctx.configEditor.entries();
574
+ const entry = entries.find((e) => e.options?.id === 'llm-pi-ai' || e.patchId === 'llm-pi-ai' || e.id === 'include:llm-pi-ai');
575
+ if (!entry) {
576
+ throw new Error('Không tìm thấy entry include:llm-pi-ai');
577
+ }
578
+
579
+ await ctx.configEditor.edit(entry, (current = {}) => {
580
+ const next = structuredClone(current);
581
+ if (!next.providers) next.providers = {};
582
+ const existing = next.providers[providerId] || {};
583
+
584
+ // Giữ lại compat tùy biến của từng model cũ nếu có
585
+ const existingCompat = new Map();
586
+ for (const m of existing.models || []) {
587
+ if (m && m.id && m.compat) existingCompat.set(m.id, m.compat);
588
+ }
589
+
590
+ const mergedModels = (providerData.models || []).map((m) => {
591
+ if (existingCompat.has(m.id)) {
592
+ return {
593
+ ...m,
594
+ compat: { ...m.compat, ...existingCompat.get(m.id) },
595
+ };
596
+ }
597
+ return m;
598
+ });
599
+
600
+ next.providers[providerId] = {
601
+ ...existing,
602
+ ...providerData,
603
+ models: mergedModels,
604
+ };
605
+
606
+ return next;
607
+ });
608
+ }
609
+
610
+ // =========================================================================
611
+ // 4. Tool: discover_custom_models
612
+ // =========================================================================
613
+ if (ctx.tools && typeof ctx.tools.register === 'function') {
614
+ ctx.tools.register({
615
+ name: 'discover_custom_models',
616
+ description: 'Tự động quét endpoint Custom API, kết hợp metadata API, models.dev và thang đo reasoning vendor chuẩn để tự gắn Vision, Thinking/Effort và Tool Calls, rồi lưu vào DSH.',
617
+ parameters: {
618
+ type: 'object',
619
+ properties: {
620
+ baseURL: {
621
+ type: 'string',
622
+ description: 'URL của API (ví dụ: https://api.openai.com/v1 hoặc https://dc-ai.dabeecao.org/v1)',
623
+ },
624
+ apiKey: {
625
+ type: 'string',
626
+ description: 'API key (nếu cần xác thực)',
627
+ },
628
+ providerId: {
629
+ type: 'string',
630
+ description: 'ID định danh cho provider trong DSH. Nếu để trống sẽ tự tạo từ hostname.',
631
+ },
632
+ api: {
633
+ type: 'string',
634
+ enum: ['openai-completions', 'openai-responses', 'anthropic-messages'],
635
+ description: 'Giao thức API. Mặc định là openai-completions.',
636
+ },
637
+ forceVision: {
638
+ type: 'boolean',
639
+ description: 'Bật Vision cho tất cả model. Mặc định false.',
640
+ },
641
+ forceThinking: {
642
+ type: 'boolean',
643
+ description: 'Bật Thinking/Reasoning effort cho tất cả model. Mặc định false.',
644
+ },
645
+ autoSave: {
646
+ type: 'boolean',
647
+ description: 'Tự động lưu vào cấu hình DSH ngay lập tức. Mặc định true.',
648
+ },
649
+ },
650
+ required: ['baseURL'],
651
+ },
652
+ output: {
653
+ schema: { type: 'object' },
654
+ render(args, value) {
655
+ return [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }];
656
+ },
657
+ },
658
+ async execute(args) {
659
+ const {
660
+ baseURL,
661
+ apiKey = '',
662
+ api = 'openai-completions',
663
+ forceVision = false,
664
+ forceThinking = false,
665
+ autoSave = true,
666
+ } = args;
667
+
668
+ let providerId = args.providerId;
669
+ if (!providerId) {
670
+ try {
671
+ const parsed = new URL(baseURL);
672
+ providerId = parsed.hostname.replace(/[^a-zA-Z0-9_-]/g, '_');
673
+ } catch {
674
+ providerId = 'custom_api';
675
+ }
676
+ }
677
+
678
+ const { url: discoveredUrl, models: rawModels } = await fetchModelsFromApi(baseURL, apiKey, api);
679
+
680
+ const analyzedModels = [];
681
+ let visionCount = 0;
682
+ let thinkingCount = 0;
683
+ let toolCount = 0;
684
+ const details = [];
685
+
686
+ for (const raw of rawModels) {
687
+ const cap = resolveCapabilities(raw, {
688
+ forceVision,
689
+ forceThinking,
690
+ api,
691
+ });
692
+
693
+ // Lọc bỏ endpoint thuần sinh ảnh (Flux/Midjourney/DALL-E) khỏi chat catalog
694
+ if (cap.isImageEndpoint) continue;
695
+
696
+ if (cap.hasVision) visionCount++;
697
+ if (cap.hasThinking) thinkingCount++;
698
+ if (cap.hasToolCall) toolCount++;
699
+
700
+ analyzedModels.push(cap.modelEntry);
701
+ details.push({
702
+ id: cap.modelEntry.id,
703
+ name: cap.modelEntry.name,
704
+ family: cap.sources.family,
705
+ vision: cap.hasVision,
706
+ thinking: cap.hasThinking,
707
+ reasoningEfforts: cap.modelEntry.reasoningEfforts,
708
+ toolCall: cap.hasToolCall,
709
+ contextWindow: cap.modelEntry.contextWindow,
710
+ maxTokens: cap.modelEntry.maxTokens,
711
+ modelsDevMatch: cap.sources.matchedInModelsDev,
712
+ });
713
+ }
714
+
715
+ let saved = false;
716
+ let saveError = null;
717
+ if (autoSave) {
718
+ try {
719
+ await updateDshProviderConfig(providerId, {
720
+ api,
721
+ baseURL: baseURL.trim().replace(/\/+$/, ''),
722
+ ...(apiKey ? { apiKeyEnv: apiKey } : {}),
723
+ displayName: providerId,
724
+ models: analyzedModels,
725
+ });
726
+ saved = true;
727
+ } catch (err) {
728
+ saveError = err.message;
729
+ }
730
+ }
731
+
732
+ return {
733
+ status: 'success',
734
+ providerId,
735
+ discoveredEndpoint: discoveredUrl,
736
+ totalChatModels: analyzedModels.length,
737
+ visionModelsCount: visionCount,
738
+ thinkingModelsCount: thinkingCount,
739
+ toolCallingModelsCount: toolCount,
740
+ savedToDsh: saved,
741
+ saveError,
742
+ models: details,
743
+ };
744
+ },
745
+ });
746
+
747
+ // =========================================================================
748
+ // 5. Tool: enhance_existing_providers
749
+ // =========================================================================
750
+ ctx.tools.register({
751
+ name: 'enhance_existing_providers',
752
+ description: 'Quét toàn bộ provider hiện có trong DSH, dùng models.dev và vendor reasoning ladder để nâng cấp Vision, Thinking và Tool Calls.',
753
+ parameters: {
754
+ type: 'object',
755
+ properties: {
756
+ providerId: {
757
+ type: 'string',
758
+ description: 'Chỉ định một provider cụ thể, hoặc để trống để nâng cấp tất cả.',
759
+ },
760
+ forceVision: {
761
+ type: 'boolean',
762
+ description: 'Bật Vision cho tất cả model. Mặc định false.',
763
+ },
764
+ forceThinking: {
765
+ type: 'boolean',
766
+ description: 'Bật Thinking cho tất cả model. Mặc định false.',
767
+ },
768
+ },
769
+ },
770
+ output: {
771
+ schema: { type: 'object' },
772
+ render(args, value) {
773
+ return [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }];
774
+ },
775
+ },
776
+ async execute(args = {}) {
777
+ if (!ctx.configEditor) {
778
+ throw new Error('configEditor service is not available');
779
+ }
780
+
781
+ const entries = ctx.configEditor.entries();
782
+ const entry = entries.find((e) => e.options?.id === 'llm-pi-ai' || e.patchId === 'llm-pi-ai' || e.id === 'include:llm-pi-ai');
783
+ if (!entry) {
784
+ throw new Error('Không tìm thấy entry include:llm-pi-ai');
785
+ }
786
+
787
+ let updatedReport = {};
788
+
789
+ await ctx.configEditor.edit(entry, (current = {}) => {
790
+ const next = structuredClone(current);
791
+ if (!next.providers) return next;
792
+
793
+ const targetProviders = args.providerId ? [args.providerId] : Object.keys(next.providers);
794
+
795
+ for (const pid of targetProviders) {
796
+ const p = next.providers[pid];
797
+ if (!p || !Array.isArray(p.models)) continue;
798
+
799
+ let modified = 0;
800
+ let visionEnhanced = 0;
801
+ let thinkingEnhanced = 0;
802
+
803
+ p.models = p.models.map((m) => {
804
+ const cap = resolveCapabilities(m, {
805
+ forceVision: args.forceVision,
806
+ forceThinking: args.forceThinking,
807
+ api: p.api,
808
+ });
809
+
810
+ const merged = {
811
+ ...cap.modelEntry,
812
+ ...m,
813
+ input: (cap.hasVision || m.input?.includes('image')) ? ['text', 'image'] : (m.input || ['text']),
814
+ contextWindow: m.contextWindow || cap.modelEntry.contextWindow,
815
+ maxTokens: Math.min(m.maxTokens || cap.modelEntry.maxTokens, m.contextWindow || cap.modelEntry.contextWindow),
816
+ };
817
+
818
+ if (cap.hasThinking && !merged.reasoningEfforts) {
819
+ merged.reasoningEfforts = cap.modelEntry.reasoningEfforts;
820
+ if (p.api === 'openai-completions') {
821
+ const s = m.id.toLowerCase();
822
+ const isDeepSeek = s.includes('deepseek') || s.includes('r1');
823
+ merged.compat = {
824
+ ...merged.compat,
825
+ supportsReasoningEffort: true,
826
+ ...(isDeepSeek ? { thinkingFormat: 'deepseek' } : {}),
827
+ };
828
+ }
829
+ }
830
+
831
+ if (merged.input.includes('image')) visionEnhanced++;
832
+ if (merged.reasoningEfforts) thinkingEnhanced++;
833
+ modified++;
834
+ return merged;
835
+ });
836
+
837
+ updatedReport[pid] = {
838
+ totalModels: p.models.length,
839
+ enhancedModels: modified,
840
+ visionModels: visionEnhanced,
841
+ thinkingModels: thinkingEnhanced,
842
+ };
843
+ }
844
+
845
+ return next;
846
+ });
847
+
848
+ return {
849
+ status: 'success',
850
+ updatedReport,
851
+ };
852
+ },
853
+ });
854
+ }
855
+
856
+ // =========================================================================
857
+ // 6. Slash Commands: /discover-models và /enhance-models
858
+ // =========================================================================
859
+ if (ctx.commands && typeof ctx.commands.register === 'function') {
860
+ ctx.commands.register({
861
+ name: 'discover-models',
862
+ description: 'Tự động phát hiện model từ Custom API (API metadata + models.dev + Vendor Ladders)',
863
+ input: {
864
+ hint: '<baseURL> [apiKey] [providerId]',
865
+ },
866
+ async handler(invocation) {
867
+ const parts = invocation.rawInput.trim().split(/\s+/).filter(Boolean);
868
+ if (parts.length === 0) {
869
+ return {
870
+ kind: 'error',
871
+ text: 'Cú pháp: /discover-models <baseURL> [apiKey] [providerId]\nVí dụ: /discover-models https://dc-ai.dabeecao.org/v1 dc_xxxx dc-ai',
872
+ };
873
+ }
874
+
875
+ const baseURL = parts[0];
876
+ const apiKey = parts[1] || '';
877
+ const providerId = parts[2] || '';
878
+
879
+ try {
880
+ const tool = ctx.tools.get('discover_custom_models');
881
+ if (!tool) throw new Error('Tool discover_custom_models chưa sẵn sàng');
882
+
883
+ const result = await ctx.tools.execute({
884
+ callId: `cmd_${Date.now()}`,
885
+ name: 'discover_custom_models',
886
+ arguments: { baseURL, apiKey, providerId, autoSave: true },
887
+ signal: invocation.signal,
888
+ });
889
+
890
+ if (result.isError) {
891
+ return { kind: 'error', text: `Thất bại: ${result.error?.message || 'Lỗi không xác định'}` };
892
+ }
893
+
894
+ const val = result.value;
895
+ return {
896
+ kind: 'success',
897
+ text: `✅ Đã quét thành công ${val.totalChatModels} chat models từ ${val.discoveredEndpoint}!\n- 🖼️ Vision models: ${val.visionModelsCount}\n- 🧠 Thinking models: ${val.thinkingModelsCount}\n- 🛠️ Tool Calling: ${val.toolCallingModelsCount}\n- 💾 Đã lưu vào DSH: ${val.savedToDsh ? 'Thành công (ID: ' + val.providerId + ')' : 'Lỗi: ' + val.saveError}`,
898
+ };
899
+ } catch (err) {
900
+ return { kind: 'error', text: `Lỗi: ${err.message}` };
901
+ }
902
+ },
903
+ });
904
+
905
+ ctx.commands.register({
906
+ name: 'enhance-models',
907
+ description: 'Nâng cấp toàn bộ model hiện có trong DSH với thang đo Vendor Thinking & Vision',
908
+ async handler(invocation) {
909
+ try {
910
+ const tool = ctx.tools.get('enhance_existing_providers');
911
+ if (!tool) throw new Error('Tool enhance_existing_providers chưa sẵn sàng');
912
+
913
+ const result = await ctx.tools.execute({
914
+ callId: `cmd_${Date.now()}`,
915
+ name: 'enhance_existing_providers',
916
+ arguments: {},
917
+ signal: invocation.signal,
918
+ });
919
+
920
+ if (result.isError) {
921
+ return { kind: 'error', text: `Thất bại: ${result.error?.message || 'Lỗi không xác định'}` };
922
+ }
923
+
924
+ return {
925
+ kind: 'success',
926
+ text: `✅ Đã nâng cấp các provider hiện có qua models.dev & vendor ladders thành công!\n${JSON.stringify(result.value.updatedReport, null, 2)}`,
927
+ };
928
+ } catch (err) {
929
+ return { kind: 'error', text: `Lỗi: ${err.message}` };
930
+ }
931
+ },
932
+ });
933
+ }
934
+ }
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Model Auto-Discovery & Capability Enhancer",
4
+ "description": "Automatically discovers custom API models and configures vision, reasoning/thinking effort, and tool calls."
5
+ }
6
+ }
package/locale/vi.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Tự động phát hiện & cấu hình Model Custom API",
4
+ "description": "Tự động phát hiện model từ Custom API và tự gắn vision, thinking/reasoning effort và tool calls."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "自定义 API 模型自动发现与能力增强",
4
+ "description": "自动发现自定义 API 端点模型,并自动挂载视觉 (Vision)、思考力度 (Thinking/Reasoning Effort) 与工具调用 (Tool Calls)。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "dsh-custom-api",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "DeepSeek Harness plugin: Auto-discovery, Vision, Reasoning/Thinking Effort & Tool Calls for Custom Model APIs",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "exports": {
8
+ ".": "./lib/index.js",
9
+ "./locale/*.json": "./locale/*.json",
10
+ "./icon": "./icon.svg"
11
+ },
12
+ "files": [
13
+ "lib",
14
+ "locale",
15
+ "cordis.patch.yml",
16
+ "icon.svg",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "keywords": [
21
+ "deepseek",
22
+ "deepseek-harness",
23
+ "dsh",
24
+ "dsh-plugin",
25
+ "custom-api",
26
+ "models-dev",
27
+ "ai-models",
28
+ "vision",
29
+ "thinking-effort",
30
+ "reasoning"
31
+ ],
32
+ "author": "6zev",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/6zev/dsh-custom-api.git"
37
+ },
38
+ "homepage": "https://github.com/6zev/dsh-custom-api#readme",
39
+ "bugs": {
40
+ "url": "https://github.com/6zev/dsh-custom-api/issues"
41
+ },
42
+ "dsh": {
43
+ "bundle": {
44
+ "patch": "./cordis.patch.yml"
45
+ }
46
+ }
47
+ }