cortico-provider-deepseek 0.1.3

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 Phantivia
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,32 @@
1
+ <!-- Owner: index.ts, pricing.ts, console/client.ts, console/server.ts, console/schedule-panel.ts, console/pricing-panel.ts, package.json -->
2
+
3
+ # cortico-provider-deepseek
4
+
5
+ `cortico-provider-deepseek` 是 Cortico 的 DeepSeek 模型供应商扩展,使用 DeepSeek Responses API。它是独立的 npm 包,不依赖 `cortico-bot-continuity`。
6
+
7
+ ## 安装
8
+
9
+ 要求 Cortico 模型供应商扩展 API 5。在 Cortico 仓库的 `extensions/` 目录安装:
10
+
11
+ ```powershell
12
+ Set-Location "<Cortico 仓库路径>\extensions"
13
+ corepack pnpm add --ignore-workspace cortico-provider-deepseek
14
+ ```
15
+
16
+ 在 Cortico 控制台中新建 DeepSeek 端点,填写 API 密钥。默认模型为 `deepseek-flash`。
17
+
18
+ ## 成本与计价
19
+
20
+ 插件为 `deepseek-flash` 提供人民币模块价目。“成本与计价”面板显示峰谷单价和当前计价档位。端点自定义报价可按模型覆盖模块价目;未设置时使用模块价目。
21
+
22
+ | 每百万 tokens | 空闲时段 | 高峰时段 |
23
+ | --- | ---: | ---: |
24
+ | 输入,缓存命中 | ¥0.02 | ¥0.04 |
25
+ | 输入,缓存未命中 | ¥1 | ¥2 |
26
+ | 输出 | ¥4 | ¥8 |
27
+
28
+ 默认高峰时段为北京时间周一至周五(不含例外日期)的 09:00–12:00、14:00–18:00。周末和例外日期全天为空闲时段。时间区间包含开始时刻,不包含结束时刻。
29
+
30
+ “当前计价时段”面板可调整两段高峰时间和例外日期;例外日期每行填写一个 `YYYY-MM-DD`。初始列表包含 2026 年中国法定节假日;后续年份需要按官方安排更新。保存后的时段设置用于新请求。
31
+
32
+ 价格可能变化,发布的单价以 [DeepSeek 中文价格页](https://api-docs.deepseek.com/zh-cn/quick_start/pricing/) 为准。
@@ -0,0 +1,9 @@
1
+ import type { ConsoleClientBundle } from 'cortico/web/shared/client-panel.ts';
2
+ import compat from 'cortico/providers/openai-responses-compat/console/client.ts';
3
+ import { pricingPanel } from './pricing-panel.ts';
4
+ import { schedulePanel } from './schedule-panel.ts';
5
+
6
+ export default {
7
+ ...compat,
8
+ panels: { ...compat.panels, schedule: schedulePanel, pricing: pricingPanel },
9
+ } satisfies ConsoleClientBundle;
@@ -0,0 +1,130 @@
1
+ import type { ConsolePanel, ConsolePanelContext } from 'cortico/web/shared/client-panel.ts';
2
+ import { pricingEditor } from 'cortico/web/client/console-pages/builtins/llm-settings/pricing-panel.ts';
3
+ import { currentPricingBand, type DeepSeekPricingSchedule } from '../pricing.ts';
4
+ import type { PriceDefinition } from 'cortico/providers/pricebook.ts';
5
+
6
+ interface PricingState {
7
+ name: string;
8
+ schedule: DeepSeekPricingSchedule;
9
+ band: 'peak' | 'offPeak';
10
+ official: PriceDefinition;
11
+ custom: unknown[];
12
+ editing: boolean;
13
+ }
14
+
15
+ interface PricingInstance { name: string }
16
+
17
+ export const pricingPanel: ConsolePanel = {
18
+ mount: async (ctx: ConsolePanelContext) => {
19
+ const zh = ctx.language === 'zh';
20
+ const S = zh ? {
21
+ title: 'DeepSeek 官方价目', current: '当前计价档位', peak: '高峰', offPeak: '空闲', model: '模型',
22
+ meter: '用量', cached: '输入(缓存命中)', uncached: '输入(缓存未命中)', output: '输出',
23
+ custom: '端点自定义报价', customOn: '已设置覆盖', fallback: '未设置,使用模块价目',
24
+ save: '应用报价', saved: '报价已写入连接草稿;保存连接后生效', savedNow: '报价已保存',
25
+ choose: 'DeepSeek 端点',
26
+ loadError: '无法读取价目', noInstances: '没有可用的 DeepSeek 端点',
27
+ } : {
28
+ title: 'DeepSeek official rates', current: 'Active pricing band', peak: 'Peak', offPeak: 'Off-peak', model: 'Model',
29
+ meter: 'Usage', cached: 'Input (cache hit)', uncached: 'Input (cache miss)', output: 'Output',
30
+ custom: 'Endpoint-specific quote', customOn: 'Override configured', fallback: 'Unset; using module rates',
31
+ save: 'Apply quote', saved: 'Quote staged; save the connection to apply it', savedNow: 'Quote saved',
32
+ choose: 'DeepSeek endpoint',
33
+ loadError: 'Unable to load rates', noInstances: 'No DeepSeek endpoints are available',
34
+ };
35
+ const message = ctx.ui.msgline();
36
+ const selector = ctx.scope.instance ? null : ctx.ui.select({
37
+ options: [],
38
+ onChange: (name) => void load(name),
39
+ });
40
+ if (selector) ctx.root.append(ctx.ui.field(S.choose, selector));
41
+ ctx.root.append(message);
42
+ let currentCard: HTMLElement | null = null;
43
+
44
+ let endpointNames: string[] = [];
45
+ if (ctx.scope.instance) {
46
+ endpointNames = [ctx.scope.instance];
47
+ } else {
48
+ try {
49
+ const instances = await ctx.invoke<PricingInstance[]>('instances');
50
+ endpointNames = instances.map(({ name }) => name);
51
+ } catch (error) {
52
+ message.textContent = `${S.loadError}: ${String(error)}`;
53
+ return;
54
+ }
55
+ if (!endpointNames.length) {
56
+ message.textContent = S.noInstances;
57
+ return;
58
+ }
59
+ selector!.replaceChildren(...endpointNames.map((name) => {
60
+ const option = ctx.ui.h('option', null, name) as HTMLOptionElement;
61
+ option.value = name;
62
+ return option;
63
+ }));
64
+ }
65
+ if (ctx.signal.aborted) return;
66
+ await load(endpointNames[0]);
67
+
68
+ async function load(name: string): Promise<void> {
69
+ try {
70
+ const state = await ctx.invoke<PricingState>('state', [{ name }]);
71
+ if (ctx.signal.aborted) return;
72
+ render(state);
73
+ message.textContent = '';
74
+ } catch (error) {
75
+ message.textContent = `${S.loadError}: ${String(error)}`;
76
+ }
77
+ }
78
+
79
+ function render(state: PricingState): void {
80
+ const card = ctx.ui.sheet({ title: S.title });
81
+ const band = currentPricingBand(state.schedule, new Date());
82
+ card.body.append(ctx.ui.statgrid([
83
+ { k: S.model, v: state.official.models.join(', ') },
84
+ { k: S.current, v: band === 'peak' ? S.peak : S.offPeak, accent: true },
85
+ ]));
86
+ const table = ctx.ui.table({ head: [S.meter, S.peak, S.offPeak] });
87
+ const peakRules = state.official.timeWindows?.[0]?.rules ?? [];
88
+ const labels = new Map([
89
+ ['cachedInput', S.cached], ['uncachedInput', S.uncached], ['output', S.output],
90
+ ]);
91
+ for (const rule of state.official.rules) {
92
+ const peak = peakRules.find((item) => item.meter === rule.meter)?.perMillion ?? rule.perMillion;
93
+ const format = (rate: number) => `¥${rate.toFixed(2)} / 1M`;
94
+ table.addRow([
95
+ labels.get(rule.meter) ?? rule.meter,
96
+ band === 'peak' ? ctx.ui.chip(format(peak), 'accent') : format(peak),
97
+ band === 'offPeak' ? ctx.ui.chip(format(rule.perMillion), 'accent') : format(rule.perMillion),
98
+ ]);
99
+ }
100
+ card.body.append(table.el, ctx.ui.msgline(`${state.official.source} · Asia/Shanghai`));
101
+
102
+ const details = ctx.ui.foldSheet('deepseek-custom-pricing', { title: S.custom });
103
+ details.el.classList.add('deepseek-custom-pricing');
104
+ details.note.textContent = state.custom.length ? S.customOn : S.fallback;
105
+ let customPricing = state.custom;
106
+ const editor = pricingEditor(ctx.ui, state.custom, [], (value) => { customPricing = value; }, ctx.language);
107
+ details.body.append(editor.body);
108
+ const save = ctx.ui.button(S.save, { variant: 'primary', onClick: () => void savePricing() });
109
+ details.body.append(ctx.ui.actions().appendChild(save));
110
+ card.body.append(details.el);
111
+ if (currentCard) currentCard.replaceWith(card.el);
112
+ else ctx.root.insertBefore(card.el, message);
113
+ currentCard = card.el;
114
+
115
+ async function savePricing(): Promise<void> {
116
+ if (!editor.validate()) return;
117
+ const release = ctx.ui.disable(save);
118
+ try {
119
+ const updated = await ctx.invoke<PricingState>('save', [{ name: state.name, pricing: customPricing }]);
120
+ message.textContent = updated.editing ? S.saved : S.savedNow;
121
+ render(updated);
122
+ } catch (error) {
123
+ message.textContent = String(error);
124
+ } finally {
125
+ release.dispose();
126
+ }
127
+ }
128
+ }
129
+ },
130
+ };
@@ -0,0 +1,97 @@
1
+ import type { ConsolePanel, ConsolePanelContext } from 'cortico/web/shared/client-panel.ts';
2
+ import { currentPricingBand, type DeepSeekPricingSchedule } from '../pricing.ts';
3
+
4
+ interface ScheduleState {
5
+ name: string;
6
+ schedule: DeepSeekPricingSchedule;
7
+ band: 'peak' | 'offPeak';
8
+ }
9
+
10
+ export const schedulePanel: ConsolePanel = {
11
+ mount: async (ctx: ConsolePanelContext) => {
12
+ const zh = ctx.language === 'zh';
13
+ const S = zh ? {
14
+ current: '当前计价时段', peak: '高峰', offPeak: '空闲', timezone: '北京时间(Asia/Shanghai)',
15
+ windows: '高峰时段', weekday: '周一至周五;周末始终为空闲时段', exceptions: '例外日期(每行 YYYY-MM-DD)',
16
+ from: '开始', to: '结束',
17
+ save: '保存时段', saved: '峰谷设置已保存', invalid: '时段格式错误;请填写 HH:MM,例外日期每行填写 YYYY-MM-DD。',
18
+ refreshError: '无法读取峰谷设置',
19
+ } : {
20
+ current: 'Active pricing band', peak: 'Peak', offPeak: 'Off-peak', timezone: 'China Standard Time (Asia/Shanghai)',
21
+ windows: 'Peak windows', weekday: 'Monday to Friday; weekends are always off-peak', exceptions: 'Exception dates (one YYYY-MM-DD per line)',
22
+ from: 'From', to: 'To',
23
+ save: 'Save schedule', saved: 'Pricing schedule saved', invalid: 'Invalid time or date format. Use HH:MM and one YYYY-MM-DD date per line.',
24
+ refreshError: 'Unable to load pricing schedule',
25
+ };
26
+ const status = ctx.ui.foldSheet('deepseek-schedule', { title: S.current });
27
+ const message = ctx.ui.msgline();
28
+ ctx.root.append(status.el, message);
29
+
30
+ let state: ScheduleState;
31
+ try {
32
+ state = await ctx.invoke<ScheduleState>('state', [{ name: ctx.scope.instance }]);
33
+ } catch (error) {
34
+ message.textContent = `${S.refreshError}: ${String(error)}`;
35
+ return;
36
+ }
37
+ if (ctx.signal.aborted) return;
38
+
39
+ const updateStatus = (band = currentPricingBand(state.schedule, new Date())) => {
40
+ const now = new Date();
41
+ const time = new Intl.DateTimeFormat(zh ? 'zh-CN' : 'en-GB', {
42
+ timeZone: 'Asia/Shanghai', dateStyle: 'medium', timeStyle: 'short', hourCycle: 'h23',
43
+ }).format(now);
44
+ status.note.textContent = `${band === 'peak' ? S.peak : S.offPeak} · ${time} · ${S.timezone}`;
45
+ };
46
+ updateStatus(state.band);
47
+ ctx.interval(updateStatus, 30_000);
48
+
49
+ const starts = state.schedule.windows.map((window) => ctx.ui.input({ value: window.from, placeholder: '09:00', cls: 'mono' }));
50
+ const ends = state.schedule.windows.map((window) => ctx.ui.input({ value: window.to, placeholder: '12:00', cls: 'mono' }));
51
+ const windows = ctx.ui.rowbar();
52
+ windows.classList.add('deepseek-window-grid');
53
+ windows.style.alignItems = 'stretch';
54
+ state.schedule.windows.forEach((_, index) => {
55
+ const group = ctx.ui.h('div', 'deepseek-window');
56
+ group.style.flex = '1 1 280px';
57
+ group.style.minWidth = '0';
58
+ const times = ctx.ui.rowbar();
59
+ times.style.flexWrap = 'nowrap';
60
+ for (const [label, input] of [[S.from, starts[index]], [S.to, ends[index]]] as const) {
61
+ const field = ctx.ui.field(label, input);
62
+ field.style.flex = '1 1 0';
63
+ field.style.minWidth = '0';
64
+ times.append(field);
65
+ }
66
+ group.append(ctx.ui.h('div', 'fieldlabel', zh ? `时段 ${index + 1}` : `Window ${index + 1}`), times);
67
+ windows.append(group);
68
+ });
69
+ status.body.append(ctx.ui.section(S.windows), ctx.ui.msgline(S.weekday), windows);
70
+ const exceptionDates = ctx.ui.textarea({ rows: 6, value: state.schedule.exceptDates.join('\n'), cls: 'mono' });
71
+ status.body.append(ctx.ui.field(S.exceptions, exceptionDates));
72
+ const save = ctx.ui.button(S.save, { variant: 'primary', onClick: () => void saveSchedule() });
73
+ status.body.append(ctx.ui.actions().appendChild(save));
74
+
75
+ async function saveSchedule(): Promise<void> {
76
+ const schedule: DeepSeekPricingSchedule = {
77
+ windows: starts.map((input, index) => ({ from: input.value.trim(), to: ends[index].value.trim() })),
78
+ exceptDates: exceptionDates.value.split(/\r?\n/).map((date) => date.trim()).filter(Boolean),
79
+ };
80
+ if (schedule.windows.some(({ from, to }) => !/^\d{2}:\d{2}$/.test(from) || !/^\d{2}:\d{2}$/.test(to))
81
+ || schedule.exceptDates.some((date) => !/^\d{4}-\d{2}-\d{2}$/.test(date))) {
82
+ message.textContent = S.invalid;
83
+ return;
84
+ }
85
+ const release = ctx.ui.disable(save);
86
+ try {
87
+ state = await ctx.invoke<ScheduleState>('save', [{ name: state.name, schedule }]);
88
+ message.textContent = S.saved;
89
+ updateStatus(state.band);
90
+ } catch (error) {
91
+ message.textContent = String(error);
92
+ } finally {
93
+ release.dispose();
94
+ }
95
+ }
96
+ },
97
+ };
@@ -0,0 +1,63 @@
1
+ import type { ConsolePageContribution } from 'cortico/web/shared/console-protocol.ts';
2
+ import type { ProviderConsoleHost } from 'cortico/providers/console/types.ts';
3
+ import type { LLMProviderEntry } from 'cortico/core/types.ts';
4
+ import responsesCompat from 'cortico/providers/openai-responses-compat/index.ts';
5
+ import { validatePrices } from 'cortico/providers/pricebook.ts';
6
+ import { currentPricingBand, parsePricingSchedule, pricesForSchedule } from '../pricing.ts';
7
+
8
+ function instanceName(args: unknown[], required: string): string {
9
+ const raw = args[0];
10
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw) || typeof (raw as Record<string, unknown>).name !== 'string') throw new Error(required);
11
+ return (raw as { name: string }).name;
12
+ }
13
+
14
+ export function deepseekPricingConsole(host: ProviderConsoleHost): Partial<ConsolePageContribution> {
15
+ const base = responsesCompat.console?.(host) ?? {};
16
+ const labels = host.language === 'zh'
17
+ ? { title: '峰谷计价', description: '查看人民币单价、当前时段并调整峰时段与例外日期。', pricing: '成本与计价', pricingDescription: 'DeepSeek 官方价目与端点自定义报价。', required: '需要端点名', unknownMethod: '未知方法' }
18
+ : { title: 'Peak/off-peak pricing', description: 'View RMB rates, the active band, and edit peak hours and exception dates.', pricing: 'Cost and pricing', pricingDescription: 'DeepSeek official rates and endpoint-specific overrides.', required: 'Endpoint name required', unknownMethod: 'Unknown method' };
19
+ const stateFor = (name: string, entry: LLMProviderEntry) => {
20
+ const schedule = parsePricingSchedule(entry.options?.deepseekPricingSchedule);
21
+ return { name, schedule, band: currentPricingBand(schedule, new Date()), official: pricesForSchedule(schedule), custom: entry.pricing ?? [] };
22
+ };
23
+ const panels = (base.panels ?? []).map((panel) => panel.id === 'pricing'
24
+ ? { id: 'pricing', title: labels.pricing, description: labels.pricingDescription }
25
+ : panel);
26
+ return {
27
+ ...base,
28
+ config: [...(base.config ?? [])],
29
+ panels: [
30
+ ...panels,
31
+ { id: 'schedule', title: labels.title, description: labels.description },
32
+ ],
33
+ invoke: async (panel, method, args) => {
34
+ if (panel !== 'schedule' && panel !== 'pricing') return base.invoke?.(panel, method, args);
35
+ if (panel === 'pricing' && method === 'instances') {
36
+ return host.entries().map(({ name }) => ({ name }));
37
+ }
38
+ const name = instanceName(args, labels.required);
39
+ const entry = host.entries().find((item) => item.name === name)?.entry;
40
+ if (!entry) throw new Error(labels.required);
41
+ if (panel === 'pricing') {
42
+ if (method === 'state') return { ...stateFor(name, entry), editing: host.editing === true };
43
+ if (method === 'save') {
44
+ const raw = args[0] as Record<string, unknown>;
45
+ const pricing = validatePrices(raw.pricing, host.language);
46
+ const next = { ...entry, pricing };
47
+ host.save(name, next);
48
+ return { ...stateFor(name, next), editing: host.editing === true };
49
+ }
50
+ throw new Error(labels.unknownMethod);
51
+ }
52
+ if (method === 'state') return stateFor(name, entry);
53
+ if (method === 'save') {
54
+ const raw = args[0] as Record<string, unknown>;
55
+ const schedule = parsePricingSchedule(raw.schedule);
56
+ const next = { ...entry, options: { ...entry.options, deepseekPricingSchedule: JSON.stringify(schedule) } };
57
+ host.save(name, next);
58
+ return stateFor(name, next);
59
+ }
60
+ throw new Error(labels.unknownMethod);
61
+ },
62
+ };
63
+ }