dsh-plugin-subscription-oauth 0.5.0 → 0.6.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/lib/auth/deepseek-balance.d.ts +58 -0
- package/lib/auth/deepseek-balance.js +63 -0
- package/lib/auth/rpc.d.ts +25 -2
- package/lib/auth/rpc.js +45 -23
- package/lib/client/UsageChip.d.ts +13 -23
- package/lib/client/UsageChip.js +27 -28
- package/lib/client/index.js +0 -14
- package/lib/client/subscriptions-rpc.d.ts +21 -20
- package/lib/client/subscriptions-rpc.js +16 -14
- package/lib/client.js +34 -38
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +12 -0
- package/lib/index.js +33 -1
- package/lib/protocol.d.ts +33 -3
- package/lib/protocol.js +1 -0
- package/lib/providers/kimi.js +2 -1
- package/lib/translate/moonshot-schema.d.ts +31 -0
- package/lib/translate/moonshot-schema.js +81 -0
- package/lib/translate/responses.d.ts +3 -1
- package/lib/translate/responses.js +4 -2
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -44,6 +44,18 @@ export interface Config {
|
|
|
44
44
|
* the other routes. Defaults to true.
|
|
45
45
|
*/
|
|
46
46
|
auto?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* The credential reference a non-subscription provider's balance query uses, by
|
|
49
|
+
* provider id.
|
|
50
|
+
*
|
|
51
|
+
* A provider that is not a subscription this plugin serves has no usage windows to
|
|
52
|
+
* meter, so the chip shows its balance instead — read by cc-switch's query, which needs
|
|
53
|
+
* the provider's API key. The provider's profile names that key by reference
|
|
54
|
+
* (`apiKeyEnv`), and the host's credential service resolves the name; this map is the
|
|
55
|
+
* one place a provider id is tied to that reference. A provider with no entry, or
|
|
56
|
+
* whose key does not resolve, gets no balance and the chip stays silent.
|
|
57
|
+
*/
|
|
58
|
+
balanceCredentialRefs?: Record<string, string>;
|
|
47
59
|
}
|
|
48
60
|
/** Configuration schema, with the config file's own defaults applied. */
|
|
49
61
|
export declare const Config: z<Config>;
|
package/lib/index.js
CHANGED
|
@@ -188,7 +188,39 @@ export function apply(ctx, config = {}) {
|
|
|
188
188
|
handles.set(route.id, handle);
|
|
189
189
|
ctx.effect(() => () => handle(), `${name}: ${route.id} provider route`);
|
|
190
190
|
}
|
|
191
|
-
|
|
191
|
+
// A non-subscription provider's balance is read by cc-switch's query, which needs the
|
|
192
|
+
// provider's API key. The provider's profile names that key by reference (`apiKeyEnv`),
|
|
193
|
+
// and the configured map ties a provider id to that reference; the host's credential
|
|
194
|
+
// service resolves it. The map lives in server config because that is where the rest
|
|
195
|
+
// of the plugin's wiring is, and the client learns a provider's balance by id, never
|
|
196
|
+
// by its credential reference.
|
|
197
|
+
const credentialRefs = config.balanceCredentialRefs ?? {};
|
|
198
|
+
// The DeepSeek account's balance is read on the node half through the host's
|
|
199
|
+
// `deepseekAccount` service; the browser reaches it through the plugin's own RPC, the
|
|
200
|
+
// same channel as the rest of the chip's data. The service is read lazily, per request
|
|
201
|
+
// (not once in apply), because it may register after this plugin applies; a composition
|
|
202
|
+
// without it still serves every other endpoint, and the balance endpoint answers nothing.
|
|
203
|
+
const balanceContext = {
|
|
204
|
+
credentialRefs,
|
|
205
|
+
resolveDeepSeekAccount: () => {
|
|
206
|
+
try {
|
|
207
|
+
return ctx.get('deepseekAccount');
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
resolveCredential: async (ref) => {
|
|
214
|
+
try {
|
|
215
|
+
const credentials = ctx.get('credentials');
|
|
216
|
+
return (await credentials?.resolve?.(ref))?.value;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
registerSubscriptionRpc(ctx, registry, balanceContext);
|
|
192
224
|
// Warm each route's live catalog for an install that is already signed in.
|
|
193
225
|
for (const id of registry.ids()) {
|
|
194
226
|
void registry.discover(id, false).catch((error) => {
|
package/lib/protocol.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
/** Prefix of every RPC method this plugin serves. */
|
|
13
13
|
export declare const RPC_PREFIX = "subscription-oauth.";
|
|
14
14
|
/** Endpoints the Settings page may call. */
|
|
15
|
-
export declare const RPC_ENDPOINTS: readonly ["status", "login", "cancel", "logout", "setDefault", "refresh", "discover", "setModelVisible", "setModelOrder", "addModel", "removeModel", "usage", "reload", "ccswitch-balance"];
|
|
15
|
+
export declare const RPC_ENDPOINTS: readonly ["status", "login", "cancel", "logout", "setDefault", "refresh", "discover", "setModelVisible", "setModelOrder", "addModel", "removeModel", "usage", "reload", "deepseek-balance", "ccswitch-balance"];
|
|
16
16
|
/** One endpoint name. */
|
|
17
17
|
export type RpcEndpoint = typeof RPC_ENDPOINTS[number];
|
|
18
18
|
/** Client → host envelope. */
|
|
@@ -313,6 +313,10 @@ export interface RpcMap {
|
|
|
313
313
|
payload: undefined;
|
|
314
314
|
value: SubscriptionStatus;
|
|
315
315
|
};
|
|
316
|
+
'deepseek-balance': {
|
|
317
|
+
payload: DeepSeekBalanceRequest;
|
|
318
|
+
value: DeepSeekBalanceResult;
|
|
319
|
+
};
|
|
316
320
|
'ccswitch-balance': {
|
|
317
321
|
payload: CcSwitchBalanceRequest;
|
|
318
322
|
value: CcSwitchBalanceResult;
|
|
@@ -343,10 +347,36 @@ export type BalanceResult =
|
|
|
343
347
|
| {
|
|
344
348
|
ok: false;
|
|
345
349
|
};
|
|
350
|
+
/** Payload of `deepseek-balance`. */
|
|
351
|
+
export interface DeepSeekBalanceRequest {
|
|
352
|
+
/** Client metadata the host's account service expects. */
|
|
353
|
+
version?: string;
|
|
354
|
+
locale?: string;
|
|
355
|
+
timezoneOffsetSeconds?: number;
|
|
356
|
+
}
|
|
357
|
+
/** One currency's wallet, as the host's account service reports it. */
|
|
358
|
+
export interface DeepSeekWallet {
|
|
359
|
+
/** ISO currency the balance is denominated in. */
|
|
360
|
+
currency: string;
|
|
361
|
+
/** Remaining balance, as a decimal string. */
|
|
362
|
+
balance: string;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Result of `deepseek-balance`.
|
|
366
|
+
*
|
|
367
|
+
* `undefined` means the account is signed out or the read failed, so a chip stays
|
|
368
|
+
* silent rather than showing a wrong figure.
|
|
369
|
+
*/
|
|
370
|
+
export type DeepSeekBalanceResult = {
|
|
371
|
+
/** Normal (topped-up) wallets, one per currency. */
|
|
372
|
+
wallets: readonly DeepSeekWallet[];
|
|
373
|
+
/** Bonus / promotional wallets, one per currency. */
|
|
374
|
+
bonusWallets: readonly DeepSeekWallet[];
|
|
375
|
+
} | undefined;
|
|
346
376
|
/** Payload of `ccswitch-balance`. */
|
|
347
377
|
export interface CcSwitchBalanceRequest {
|
|
348
|
-
/** The
|
|
349
|
-
|
|
378
|
+
/** The provider whose balance to read; the server maps it to its credential reference. */
|
|
379
|
+
provider: string;
|
|
350
380
|
}
|
|
351
381
|
/**
|
|
352
382
|
* Result of `ccswitch-balance`.
|
package/lib/protocol.js
CHANGED
package/lib/providers/kimi.js
CHANGED
|
@@ -14,6 +14,7 @@ import { idleWatchdog, httpLlmError, isMissingOrInvalidCredential, mapFetchFailu
|
|
|
14
14
|
import { streamProviderResponses } from './responses-stream.js';
|
|
15
15
|
import { fallbackEntry, findEntry, resolveCatalog } from './catalog.js';
|
|
16
16
|
import { streamResponses, toResponsesInput, toResponsesTools } from '../translate/responses.js';
|
|
17
|
+
import { moonshotToolParameters } from '../translate/moonshot-schema.js';
|
|
17
18
|
/**
|
|
18
19
|
* Coerce one decoded credential entry into a Kimi session.
|
|
19
20
|
* @param value - decoded JSON value for one account.
|
|
@@ -310,7 +311,7 @@ export class KimiAdapter extends LlmAdapter {
|
|
|
310
311
|
input: resolved.input,
|
|
311
312
|
...options.tools === undefined || options.tools.length === 0
|
|
312
313
|
? {}
|
|
313
|
-
: { tools: toResponsesTools(options.tools), tool_choice: 'auto', parallel_tool_calls: true },
|
|
314
|
+
: { tools: toResponsesTools(options.tools, moonshotToolParameters), tool_choice: 'auto', parallel_tool_calls: true },
|
|
314
315
|
...options.maxTokens === undefined ? {} : { max_output_tokens: options.maxTokens },
|
|
315
316
|
...options.reasoningEffort === undefined
|
|
316
317
|
? {}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-schema preparation for the Moonshot wire (Kimi Code).
|
|
3
|
+
*
|
|
4
|
+
* Moonshot validates `tools[].function.parameters` with its own JSON-Schema
|
|
5
|
+
* reader, and that reader cannot settle a `$ref` that carries sibling
|
|
6
|
+
* keywords: instead of intersecting the two, it re-expands the node, so the
|
|
7
|
+
* whole request is refused with
|
|
8
|
+
* `detected infinite recursion without termination condition`. The harness
|
|
9
|
+
* forwards tool schemas exactly as their owner declared them, and the MCP
|
|
10
|
+
* tools REA registers use that shape heavily —
|
|
11
|
+
* `{"$ref": "#/$defs/__schema12", "description": "..."}` — 153 of 170 tools
|
|
12
|
+
* carried at least one such node, and the first one Moonshot reached killed
|
|
13
|
+
* every Kimi request that included a tool.
|
|
14
|
+
*
|
|
15
|
+
* A `$ref` that stands alone is accepted, and so is keeping `$defs` for it, so
|
|
16
|
+
* only the offending nodes are rewritten: the referenced schema is inlined
|
|
17
|
+
* under the node's own keywords (JSON Schema 2020-12 applies both, and the
|
|
18
|
+
* node's own keywords win on a conflict), which leaves the wire schema with
|
|
19
|
+
* standalone references only. Resolution is bounded by the reference stack, so
|
|
20
|
+
* a real cycle ends by dropping the `$ref` and keeping the sibling keywords
|
|
21
|
+
* rather than recursing.
|
|
22
|
+
*
|
|
23
|
+
* @module dsh-plugin-subscription-oauth/translate/moonshot-schema
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Prepare one tool's parameters for Moonshot.
|
|
27
|
+
*
|
|
28
|
+
* @param parameters - the tool's declared argument schema.
|
|
29
|
+
* @returns a detached schema whose `$ref` nodes carry no sibling keywords.
|
|
30
|
+
*/
|
|
31
|
+
export declare function moonshotToolParameters(parameters: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool-schema preparation for the Moonshot wire (Kimi Code).
|
|
3
|
+
*
|
|
4
|
+
* Moonshot validates `tools[].function.parameters` with its own JSON-Schema
|
|
5
|
+
* reader, and that reader cannot settle a `$ref` that carries sibling
|
|
6
|
+
* keywords: instead of intersecting the two, it re-expands the node, so the
|
|
7
|
+
* whole request is refused with
|
|
8
|
+
* `detected infinite recursion without termination condition`. The harness
|
|
9
|
+
* forwards tool schemas exactly as their owner declared them, and the MCP
|
|
10
|
+
* tools REA registers use that shape heavily —
|
|
11
|
+
* `{"$ref": "#/$defs/__schema12", "description": "..."}` — 153 of 170 tools
|
|
12
|
+
* carried at least one such node, and the first one Moonshot reached killed
|
|
13
|
+
* every Kimi request that included a tool.
|
|
14
|
+
*
|
|
15
|
+
* A `$ref` that stands alone is accepted, and so is keeping `$defs` for it, so
|
|
16
|
+
* only the offending nodes are rewritten: the referenced schema is inlined
|
|
17
|
+
* under the node's own keywords (JSON Schema 2020-12 applies both, and the
|
|
18
|
+
* node's own keywords win on a conflict), which leaves the wire schema with
|
|
19
|
+
* standalone references only. Resolution is bounded by the reference stack, so
|
|
20
|
+
* a real cycle ends by dropping the `$ref` and keeping the sibling keywords
|
|
21
|
+
* rather than recursing.
|
|
22
|
+
*
|
|
23
|
+
* @module dsh-plugin-subscription-oauth/translate/moonshot-schema
|
|
24
|
+
*/
|
|
25
|
+
/** True for a plain object usable as a schema node. */
|
|
26
|
+
function object(value) {
|
|
27
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
/** Follow one local JSON pointer, or undefined when it does not resolve. */
|
|
30
|
+
function pointed(root, ref) {
|
|
31
|
+
if (!ref.startsWith('#/'))
|
|
32
|
+
return undefined;
|
|
33
|
+
let node = root;
|
|
34
|
+
for (const segment of ref.slice(2).split('/')) {
|
|
35
|
+
const key = segment.replaceAll('~1', '/').replaceAll('~0', '~');
|
|
36
|
+
if (Array.isArray(node))
|
|
37
|
+
node = node[Number(key)];
|
|
38
|
+
else if (object(node) && Object.hasOwn(node, key))
|
|
39
|
+
node = node[key];
|
|
40
|
+
else
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return node;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Rewrite one node so every `$ref` left on the wire stands alone.
|
|
47
|
+
*
|
|
48
|
+
* @param node - the node to rewrite.
|
|
49
|
+
* @param root - the tool's root schema, which local pointers resolve against.
|
|
50
|
+
* @param refs - pointers already being resolved on the path to this node.
|
|
51
|
+
* @returns the rewritten node.
|
|
52
|
+
*/
|
|
53
|
+
function inline(node, root, refs) {
|
|
54
|
+
if (Array.isArray(node))
|
|
55
|
+
return node.map((item) => inline(item, root, refs));
|
|
56
|
+
if (!object(node))
|
|
57
|
+
return node;
|
|
58
|
+
const { $ref, ...siblings } = node;
|
|
59
|
+
if (typeof $ref !== 'string') {
|
|
60
|
+
return Object.fromEntries(Object.entries(node).map(([key, value]) => [key, inline(value, root, refs)]));
|
|
61
|
+
}
|
|
62
|
+
// A standalone reference is exactly what Moonshot expects, so it stays as is
|
|
63
|
+
// and keeps its `$defs` entry.
|
|
64
|
+
if (Object.keys(siblings).length === 0)
|
|
65
|
+
return { $ref };
|
|
66
|
+
// A repeated pointer, or one that is not a local object, cannot be inlined:
|
|
67
|
+
// the sibling keywords are all that is left to terminate on.
|
|
68
|
+
const target = refs.has($ref) ? undefined : pointed(root, $ref);
|
|
69
|
+
return object(target)
|
|
70
|
+
? inline({ ...target, ...siblings }, root, new Set([...refs, $ref]))
|
|
71
|
+
: inline(siblings, root, refs);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Prepare one tool's parameters for Moonshot.
|
|
75
|
+
*
|
|
76
|
+
* @param parameters - the tool's declared argument schema.
|
|
77
|
+
* @returns a detached schema whose `$ref` nodes carry no sibling keywords.
|
|
78
|
+
*/
|
|
79
|
+
export function moonshotToolParameters(parameters) {
|
|
80
|
+
return inline(parameters, parameters, new Set());
|
|
81
|
+
}
|
|
@@ -53,9 +53,11 @@ export declare function toResponsesInput(messages: readonly RequestMessage[], sy
|
|
|
53
53
|
* than letting the endpoint normalize every schema into strict mode.
|
|
54
54
|
*
|
|
55
55
|
* @param tools - the request's tool schemas.
|
|
56
|
+
* @param prepare - an endpoint-specific rewrite of one parameters schema, for
|
|
57
|
+
* a route whose JSON-Schema reader refuses the schema as declared.
|
|
56
58
|
* @returns `tools` array entries.
|
|
57
59
|
*/
|
|
58
|
-
export declare function toResponsesTools(tools: readonly ToolSchema[]): Record<string, unknown>[];
|
|
60
|
+
export declare function toResponsesTools(tools: readonly ToolSchema[], prepare?: (parameters: Record<string, unknown>) => Record<string, unknown>): Record<string, unknown>[];
|
|
59
61
|
/** The `usage` object of a completed response. */
|
|
60
62
|
export interface ResponsesUsage {
|
|
61
63
|
input_tokens?: number;
|
|
@@ -124,14 +124,16 @@ export async function toResponsesInput(messages, system, resolveImage) {
|
|
|
124
124
|
* than letting the endpoint normalize every schema into strict mode.
|
|
125
125
|
*
|
|
126
126
|
* @param tools - the request's tool schemas.
|
|
127
|
+
* @param prepare - an endpoint-specific rewrite of one parameters schema, for
|
|
128
|
+
* a route whose JSON-Schema reader refuses the schema as declared.
|
|
127
129
|
* @returns `tools` array entries.
|
|
128
130
|
*/
|
|
129
|
-
export function toResponsesTools(tools) {
|
|
131
|
+
export function toResponsesTools(tools, prepare) {
|
|
130
132
|
return tools.map((tool) => ({
|
|
131
133
|
type: 'function',
|
|
132
134
|
name: tool.name,
|
|
133
135
|
description: tool.description,
|
|
134
|
-
parameters: tool.parameters,
|
|
136
|
+
parameters: prepare === undefined ? tool.parameters : prepare(tool.parameters),
|
|
135
137
|
strict: false,
|
|
136
138
|
}));
|
|
137
139
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-subscription-oauth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Use subscription accounts as DeepSeek Harness LLM providers — Kimi Code (OAuth device flow), Grok / X Premium (OIDC + PKCE), Google Antigravity (Google OAuth + Cloud Code Assist) and GLM / BigModel (ZCode's browser sign-in) — with per-model visibility, drag-to-reorder Auto fallback and plan quotas from the web Settings page",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "EricFL1998",
|