saros-proxy 0.7.3 → 0.9.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/README.md +98 -47
- package/dist/cli/autostart.js.map +1 -1
- package/dist/cli/daemon.d.ts +31 -4
- package/dist/cli/daemon.js +124 -22
- package/dist/cli/daemon.js.map +1 -1
- package/dist/cli/dsh-config.d.ts +32 -0
- package/dist/cli/dsh-config.js +122 -0
- package/dist/cli/dsh-config.js.map +1 -0
- package/dist/cli/harness-models.d.ts +26 -2
- package/dist/cli/harness-models.js +192 -2
- package/dist/cli/harness-models.js.map +1 -1
- package/dist/cli/harness-sync.d.ts +27 -10
- package/dist/cli/harness-sync.js +70 -16
- package/dist/cli/harness-sync.js.map +1 -1
- package/dist/cli/help.js +4 -2
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/omp-config.js +22 -5
- package/dist/cli/omp-config.js.map +1 -1
- package/dist/cli/opencode-config.js +22 -15
- package/dist/cli/opencode-config.js.map +1 -1
- package/dist/cli/overview.d.ts +10 -0
- package/dist/cli/overview.js +61 -0
- package/dist/cli/overview.js.map +1 -0
- package/dist/cli/pi-config.js +21 -5
- package/dist/cli/pi-config.js.map +1 -1
- package/dist/cli/setup.d.ts +6 -10
- package/dist/cli/setup.js +107 -233
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/ui.d.ts +0 -1
- package/dist/cli/ui.js +0 -16
- package/dist/cli/ui.js.map +1 -1
- package/dist/cli/update-check.js.map +1 -1
- package/dist/cli/usage.d.ts +12 -0
- package/dist/cli/usage.js +116 -0
- package/dist/cli/usage.js.map +1 -0
- package/dist/config.d.ts +13 -17
- package/dist/config.js +29 -78
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +7 -6
- package/dist/constants.js +10 -5
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +34 -34
- package/dist/index.js.map +1 -1
- package/dist/model-stats.d.ts +20 -0
- package/dist/model-stats.js +135 -0
- package/dist/model-stats.js.map +1 -0
- package/dist/models-sync.js +10 -4
- package/dist/models-sync.js.map +1 -1
- package/dist/probe-cache.js.map +1 -1
- package/dist/providers/commandcode.d.ts +17 -0
- package/dist/providers/commandcode.js +232 -0
- package/dist/providers/commandcode.js.map +1 -0
- package/dist/providers/index.d.ts +43 -0
- package/dist/providers/index.js +75 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/infer.d.ts +23 -0
- package/dist/providers/infer.js +13 -0
- package/dist/providers/infer.js.map +1 -0
- package/dist/providers/opencode-go.d.ts +4 -0
- package/dist/providers/opencode-go.js +179 -0
- package/dist/providers/opencode-go.js.map +1 -0
- package/dist/providers/types.d.ts +118 -0
- package/dist/providers/types.js +10 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/proxy-logic.d.ts +46 -22
- package/dist/proxy-logic.js +146 -106
- package/dist/proxy-logic.js.map +1 -1
- package/dist/proxy.d.ts +20 -2
- package/dist/proxy.js +231 -57
- package/dist/proxy.js.map +1 -1
- package/dist/usage-client.d.ts +41 -0
- package/dist/usage-client.js +124 -0
- package/dist/usage-client.js.map +1 -0
- package/dist/validation.d.ts +5 -5
- package/dist/validation.js +7 -9
- package/dist/validation.js.map +1 -1
- package/package.json +75 -77
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-config.ts — DeepSeek Harness (dsh) sync target.
|
|
3
|
+
*
|
|
4
|
+
* Updates the saros-proxy provider's model list inside the dsh profile's
|
|
5
|
+
* patch layer (~/.dsh/profiles/web/cordis.patch.yml), row id `llm-pi-ai`.
|
|
6
|
+
* dsh model entries use the pi/omp field names plus a `reasoningEfforts`
|
|
7
|
+
* map ({ 'off': null, high: 'high', … }) instead of pi/omp's `thinking`
|
|
8
|
+
* object — the transformer here converts the canonical entries.
|
|
9
|
+
*
|
|
10
|
+
* Only `providers["saros-proxy"].models` is replaced; every other provider
|
|
11
|
+
* row (commandcode, commandcode-anthropic, agent-default-model, MCP inserts)
|
|
12
|
+
* and all user fields are preserved. Leading comment lines are re-emitted
|
|
13
|
+
* verbatim (the patch file documents its `!!js` allowance in a comment).
|
|
14
|
+
* cordis.yml is NEVER touched (dsh rewrites it empty at every boot).
|
|
15
|
+
*/
|
|
16
|
+
import { type PiOmpModel } from './harness-models.js';
|
|
17
|
+
import type { OpencodeConfigResult } from './opencode-config.js';
|
|
18
|
+
/** Default dsh patch layer path for the web profile. */
|
|
19
|
+
export declare function getDefaultDshConfigPath(): string;
|
|
20
|
+
/** Convert a pi/omp model entry into the dsh patch entry shape. */
|
|
21
|
+
export declare function toDshModel(model: PiOmpModel): Record<string, unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* Sync the saros-proxy provider model list into the dsh patch layer.
|
|
24
|
+
*
|
|
25
|
+
* @param modelsMap — Canonical model map (id → opencode-shaped entry)
|
|
26
|
+
* @param port — Proxy port (unused today; kept for harness-sync parity)
|
|
27
|
+
* @param options.configPath — Explicit config path (for testing)
|
|
28
|
+
* @returns Result with success status and path
|
|
29
|
+
*/
|
|
30
|
+
export declare function syncModelsToDshConfig(modelsMap: Record<string, unknown>, port: number, options?: {
|
|
31
|
+
configPath?: string;
|
|
32
|
+
}): OpencodeConfigResult;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-config.ts — DeepSeek Harness (dsh) sync target.
|
|
3
|
+
*
|
|
4
|
+
* Updates the saros-proxy provider's model list inside the dsh profile's
|
|
5
|
+
* patch layer (~/.dsh/profiles/web/cordis.patch.yml), row id `llm-pi-ai`.
|
|
6
|
+
* dsh model entries use the pi/omp field names plus a `reasoningEfforts`
|
|
7
|
+
* map ({ 'off': null, high: 'high', … }) instead of pi/omp's `thinking`
|
|
8
|
+
* object — the transformer here converts the canonical entries.
|
|
9
|
+
*
|
|
10
|
+
* Only `providers["saros-proxy"].models` is replaced; every other provider
|
|
11
|
+
* row (commandcode, commandcode-anthropic, agent-default-model, MCP inserts)
|
|
12
|
+
* and all user fields are preserved. Leading comment lines are re-emitted
|
|
13
|
+
* verbatim (the patch file documents its `!!js` allowance in a comment).
|
|
14
|
+
* cordis.yml is NEVER touched (dsh rewrites it empty at every boot).
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
20
|
+
import { toPiOmpModelArray } from './harness-models.js';
|
|
21
|
+
/** Default dsh patch layer path for the web profile. */
|
|
22
|
+
export function getDefaultDshConfigPath() {
|
|
23
|
+
return join(homedir(), '.dsh', 'profiles', 'web', 'cordis.patch.yml');
|
|
24
|
+
}
|
|
25
|
+
const DSH_ROW_ID = 'llm-pi-ai';
|
|
26
|
+
const SAROS_PROVIDER_KEY = 'saros-proxy';
|
|
27
|
+
/** Convert a pi/omp model entry into the dsh patch entry shape. */
|
|
28
|
+
export function toDshModel(model) {
|
|
29
|
+
const entry = {
|
|
30
|
+
id: model.id,
|
|
31
|
+
name: model.name,
|
|
32
|
+
input: model.input,
|
|
33
|
+
contextWindow: model.contextWindow,
|
|
34
|
+
maxTokens: model.maxTokens,
|
|
35
|
+
};
|
|
36
|
+
if (model.thinking && model.thinking.efforts.length > 0) {
|
|
37
|
+
const efforts = { off: null };
|
|
38
|
+
for (const effort of model.thinking.efforts) {
|
|
39
|
+
efforts[effort] = effort;
|
|
40
|
+
}
|
|
41
|
+
entry.reasoningEfforts = efforts;
|
|
42
|
+
}
|
|
43
|
+
if (model.cost) {
|
|
44
|
+
entry.cost = model.cost;
|
|
45
|
+
}
|
|
46
|
+
return entry;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Split leading comment lines from the raw YAML text so they can be
|
|
50
|
+
* re-emitted after stringify (the yaml package drops comments).
|
|
51
|
+
*/
|
|
52
|
+
function splitLeadingComments(raw) {
|
|
53
|
+
const lines = raw.split('\n');
|
|
54
|
+
const comments = [];
|
|
55
|
+
let i = 0;
|
|
56
|
+
while (i < lines.length && (lines[i].trimStart().startsWith('#') || lines[i].trim() === '')) {
|
|
57
|
+
comments.push(lines[i]);
|
|
58
|
+
i++;
|
|
59
|
+
}
|
|
60
|
+
return { comments: comments.join('\n'), body: lines.slice(i).join('\n') };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Sync the saros-proxy provider model list into the dsh patch layer.
|
|
64
|
+
*
|
|
65
|
+
* @param modelsMap — Canonical model map (id → opencode-shaped entry)
|
|
66
|
+
* @param port — Proxy port (unused today; kept for harness-sync parity)
|
|
67
|
+
* @param options.configPath — Explicit config path (for testing)
|
|
68
|
+
* @returns Result with success status and path
|
|
69
|
+
*/
|
|
70
|
+
export function syncModelsToDshConfig(modelsMap, port, options = {}) {
|
|
71
|
+
void port;
|
|
72
|
+
const configPath = options.configPath ?? getDefaultDshConfigPath();
|
|
73
|
+
if (!existsSync(configPath)) {
|
|
74
|
+
return { success: false, error: 'config not found at ' + configPath };
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
78
|
+
const { comments, body } = splitLeadingComments(raw);
|
|
79
|
+
let doc;
|
|
80
|
+
try {
|
|
81
|
+
doc = parseYaml(body);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return { success: false, path: configPath, error: 'dsh patch contains invalid YAML' };
|
|
85
|
+
}
|
|
86
|
+
if (!Array.isArray(doc)) {
|
|
87
|
+
return { success: false, path: configPath, error: 'dsh patch is not a top-level array' };
|
|
88
|
+
}
|
|
89
|
+
// Locate the llm-pi-ai row and its saros-proxy provider entry.
|
|
90
|
+
const row = doc.find((r) => r && typeof r === 'object' && !Array.isArray(r) &&
|
|
91
|
+
r.id === DSH_ROW_ID);
|
|
92
|
+
if (!row) {
|
|
93
|
+
return { success: false, path: configPath, error: `dsh patch has no ${DSH_ROW_ID} row` };
|
|
94
|
+
}
|
|
95
|
+
const config = row.config;
|
|
96
|
+
const providers = config?.providers;
|
|
97
|
+
const provider = providers?.[SAROS_PROVIDER_KEY];
|
|
98
|
+
if (!provider) {
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
path: configPath,
|
|
102
|
+
error: `dsh patch ${DSH_ROW_ID} row has no ${SAROS_PROVIDER_KEY} provider`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// Replace ONLY the models key.
|
|
106
|
+
provider.models = toPiOmpModelArray(modelsMap).map(toDshModel);
|
|
107
|
+
const backupPath = configPath + '.backup';
|
|
108
|
+
if (existsSync(configPath)) {
|
|
109
|
+
copyFileSync(configPath, backupPath);
|
|
110
|
+
}
|
|
111
|
+
writeFileSync(configPath, comments ? comments + '\n' + stringifyYaml(doc) : stringifyYaml(doc), 'utf-8');
|
|
112
|
+
return { success: true, path: configPath, created: false };
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
return {
|
|
116
|
+
success: false,
|
|
117
|
+
path: configPath,
|
|
118
|
+
error: err instanceof Error ? err.message : String(err),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=dsh-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dsh-config.js","sourceRoot":"","sources":["../../src/cli/dsh-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
* upstream + models.dev metadata (falling back to bundled models.json).
|
|
9
9
|
*/
|
|
10
10
|
import type { ProxyConfig } from '../config.js';
|
|
11
|
+
/**
|
|
12
|
+
* Suffix for provider-specific model ids in synced harness configs.
|
|
13
|
+
* Commandcode-only models (claude-*, vendor/model) are exposed to harnesses
|
|
14
|
+
* as `<id>@commandcode`; the proxy strips the suffix on the way upstream.
|
|
15
|
+
*/
|
|
16
|
+
export declare const PROVIDER_MODEL_SUFFIX = "@";
|
|
11
17
|
export interface PiOmpModel {
|
|
12
18
|
id: string;
|
|
13
19
|
name: string;
|
|
@@ -21,6 +27,11 @@ export interface PiOmpModel {
|
|
|
21
27
|
cacheRead: number;
|
|
22
28
|
cacheWrite: number;
|
|
23
29
|
};
|
|
30
|
+
/** Explicit thinking surface (pi/omp schema). Absent → harness infers one. */
|
|
31
|
+
thinking?: {
|
|
32
|
+
mode: 'effort';
|
|
33
|
+
efforts: string[];
|
|
34
|
+
};
|
|
24
35
|
}
|
|
25
36
|
/**
|
|
26
37
|
* Transform one canonical (opencode-shaped) model entry into the
|
|
@@ -30,12 +41,25 @@ export interface PiOmpModel {
|
|
|
30
41
|
export declare function toPiOmpModel(entry: Record<string, unknown>): PiOmpModel;
|
|
31
42
|
/** Transform the canonical model map into a pi/omp `models` array. */
|
|
32
43
|
export declare function toPiOmpModelArray(models: Record<string, unknown>): PiOmpModel[];
|
|
44
|
+
/**
|
|
45
|
+
* Normalize a provider model id to its bare name for cross-provider
|
|
46
|
+
* matching: 'deepseek/deepseek-v4-flash' → 'deepseek-v4-flash';
|
|
47
|
+
* 'glm-5' → 'glm-5'. Case- and separator-insensitive.
|
|
48
|
+
*/
|
|
49
|
+
export declare function bareModelName(modelId: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Annotate a display name with how many keys can serve the model,
|
|
52
|
+
* e.g. "DeepSeek V4 Flash (3)". Communicates rotation width: '(3)' rotates
|
|
53
|
+
* across 3 accounts (possibly cross-provider), '(1)' is a single key.
|
|
54
|
+
*/
|
|
55
|
+
export declare function annotateName(name: string, keyCount: number): string;
|
|
33
56
|
/**
|
|
34
57
|
* Build the canonical model map (id → opencode-shaped entry).
|
|
35
58
|
*
|
|
36
59
|
* Live path (config provided, not offline): fetch upstream model IDs and
|
|
37
|
-
* enrich each with models.dev metadata via buildMinimalStub
|
|
38
|
-
*
|
|
60
|
+
* enrich each with models.dev metadata via buildMinimalStub, then append
|
|
61
|
+
* provider-specific catalog models (@commandcode etc.). Falls back to the
|
|
62
|
+
* bundled models.json when the upstream fetch yields nothing.
|
|
39
63
|
* Offline path (or no config): bundled models.json only.
|
|
40
64
|
*/
|
|
41
65
|
export declare function buildCanonicalModels(config?: ProxyConfig, opts?: {
|
|
@@ -9,6 +9,40 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { loadModelsFromJson } from './opencode-config.js';
|
|
11
11
|
import { buildMinimalStub, fetchModelsDevMetadata, fetchUpstreamModelIds } from '../models-sync.js';
|
|
12
|
+
import { allProviders, getProvider, inferProvider } from '../providers/index.js';
|
|
13
|
+
/**
|
|
14
|
+
* Suffix for provider-specific model ids in synced harness configs.
|
|
15
|
+
* Commandcode-only models (claude-*, vendor/model) are exposed to harnesses
|
|
16
|
+
* as `<id>@commandcode`; the proxy strips the suffix on the way upstream.
|
|
17
|
+
*/
|
|
18
|
+
export const PROVIDER_MODEL_SUFFIX = '@';
|
|
19
|
+
/** Canonical effort vocabulary, least → most intensive (pi/omp schema). */
|
|
20
|
+
const EFFORT_ORDER = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
|
|
21
|
+
/**
|
|
22
|
+
* Extract the effort ladder from models.dev `reasoning_options`
|
|
23
|
+
* (`[{ type: 'effort', values: [...] }]`). Keeps only values in the pi/omp
|
|
24
|
+
* thinking schema vocabulary; deduplicates and orders least → most intensive.
|
|
25
|
+
*/
|
|
26
|
+
function parseEfforts(entry) {
|
|
27
|
+
const options = entry.reasoning_options;
|
|
28
|
+
if (!Array.isArray(options))
|
|
29
|
+
return [];
|
|
30
|
+
const found = new Set();
|
|
31
|
+
for (const option of options) {
|
|
32
|
+
if (!option || typeof option !== 'object' || Array.isArray(option))
|
|
33
|
+
continue;
|
|
34
|
+
if (option.type !== 'effort')
|
|
35
|
+
continue;
|
|
36
|
+
const values = option.values;
|
|
37
|
+
if (!Array.isArray(values))
|
|
38
|
+
continue;
|
|
39
|
+
for (const value of values) {
|
|
40
|
+
if (typeof value === 'string')
|
|
41
|
+
found.add(value);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return EFFORT_ORDER.filter((effort) => found.has(effort));
|
|
45
|
+
}
|
|
12
46
|
/**
|
|
13
47
|
* Transform one canonical (opencode-shaped) model entry into the
|
|
14
48
|
* pi/omp model shape. Unknown metadata falls back to safe defaults
|
|
@@ -28,6 +62,15 @@ export function toPiOmpModel(entry) {
|
|
|
28
62
|
contextWindow: typeof limit.context === 'number' ? limit.context : 262144,
|
|
29
63
|
maxTokens: typeof limit.output === 'number' ? limit.output : 65536,
|
|
30
64
|
};
|
|
65
|
+
// Explicit thinking metadata: models.dev effort options → pi/omp
|
|
66
|
+
// `thinking.efforts`. Without it the harness infers a generic ladder that
|
|
67
|
+
// can disagree with the model's real contract (e.g. ox-alpha only accepts
|
|
68
|
+
// low/high/max).
|
|
69
|
+
if (model.reasoning) {
|
|
70
|
+
const efforts = parseEfforts(entry);
|
|
71
|
+
if (efforts.length > 0)
|
|
72
|
+
model.thinking = { mode: 'effort', efforts };
|
|
73
|
+
}
|
|
31
74
|
// Cost is optional; accept both camelCase (pi/omp) and snake_case (models.dev).
|
|
32
75
|
const rawCost = entry.cost;
|
|
33
76
|
if (rawCost && typeof rawCost === 'object' && !Array.isArray(rawCost)) {
|
|
@@ -49,12 +92,144 @@ export function toPiOmpModel(entry) {
|
|
|
49
92
|
export function toPiOmpModelArray(models) {
|
|
50
93
|
return Object.values(models).map((entry) => toPiOmpModel(entry));
|
|
51
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Normalize a provider model id to its bare name for cross-provider
|
|
97
|
+
* matching: 'deepseek/deepseek-v4-flash' → 'deepseek-v4-flash';
|
|
98
|
+
* 'glm-5' → 'glm-5'. Case- and separator-insensitive.
|
|
99
|
+
*/
|
|
100
|
+
export function bareModelName(modelId) {
|
|
101
|
+
const last = modelId.split('/').pop() ?? modelId;
|
|
102
|
+
return last.toLowerCase().replaceAll('_', '-');
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Annotate a display name with how many keys can serve the model,
|
|
106
|
+
* e.g. "DeepSeek V4 Flash (3)". Communicates rotation width: '(3)' rotates
|
|
107
|
+
* across 3 accounts (possibly cross-provider), '(1)' is a single key.
|
|
108
|
+
*/
|
|
109
|
+
export function annotateName(name, keyCount) {
|
|
110
|
+
if (keyCount <= 0)
|
|
111
|
+
return name;
|
|
112
|
+
return `${name} (${keyCount})`;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Count how many of the user's keys can serve a bare model name.
|
|
116
|
+
*
|
|
117
|
+
* The owning provider (whose catalog the entry came from) always serves it.
|
|
118
|
+
* Every OTHER provider must claim the model definitively ('yes' via
|
|
119
|
+
* bare-name/affinity check) — a structural 'maybe' does not count, or
|
|
120
|
+
* blanket-maybe providers (opencode-go) would overcount models they don't
|
|
121
|
+
* actually have (e.g. claude-*).
|
|
122
|
+
*/
|
|
123
|
+
function countServingKeys(config, bareName, owner) {
|
|
124
|
+
return (config.keys ?? []).filter((k) => {
|
|
125
|
+
const provider = getProvider(inferProvider(k));
|
|
126
|
+
if (!provider)
|
|
127
|
+
return false;
|
|
128
|
+
if (provider.id === owner)
|
|
129
|
+
return true;
|
|
130
|
+
const byName = provider.modelAffinityByName?.(bareName);
|
|
131
|
+
if (byName)
|
|
132
|
+
return byName === 'yes';
|
|
133
|
+
return provider.modelAffinity(bareName) === 'yes';
|
|
134
|
+
}).length;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Map one provider-catalog entry into the canonical opencode-shaped entry.
|
|
138
|
+
* Uses the catalog's own fields when present (commandcode serves name +
|
|
139
|
+
* context_length); models.dev metadata is not available for these providers.
|
|
140
|
+
*/
|
|
141
|
+
/**
|
|
142
|
+
* Map one provider-catalog entry into the canonical opencode-shaped entry.
|
|
143
|
+
* Uses the catalog's own fields when present (commandcode serves name +
|
|
144
|
+
* context_length); models.dev metadata is not available for these providers.
|
|
145
|
+
*/
|
|
146
|
+
function providerCatalogToCanonical(modelId, entry, keyCount) {
|
|
147
|
+
const context = typeof entry.context_length === 'number' ? entry.context_length : undefined;
|
|
148
|
+
const name = typeof entry.name === 'string' ? entry.name : modelId;
|
|
149
|
+
return {
|
|
150
|
+
id: modelId,
|
|
151
|
+
name: annotateName(name, keyCount),
|
|
152
|
+
tool_call: true,
|
|
153
|
+
reasoning: true,
|
|
154
|
+
limit: {
|
|
155
|
+
context: context ?? 262144,
|
|
156
|
+
output: 65536,
|
|
157
|
+
},
|
|
158
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
159
|
+
// Conservative effort ladder for provider-catalog models (no models.dev
|
|
160
|
+
// metadata): the commandcode gateway accepts low|medium|high|xhigh and
|
|
161
|
+
// rejects minimal — better declared here than invented by the harness.
|
|
162
|
+
reasoning_options: [{ type: 'effort', values: ['low', 'medium', 'high'] }],
|
|
163
|
+
// Hint consumed by display tooling; harmless to harnesses that ignore it.
|
|
164
|
+
...(typeof entry.owned_by === 'string' ? { owned_by: entry.owned_by } : {}),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Fetch model entries from the given providers and merge them into the map.
|
|
169
|
+
*
|
|
170
|
+
* Cross-provider aliasing: when a provider catalog serves the SAME model
|
|
171
|
+
* under a different id (bare name matches an existing entry — e.g.
|
|
172
|
+
* 'deepseek/deepseek-v4-flash' on commandcode vs 'deepseek-v4-flash' on
|
|
173
|
+
* opencode-go), the existing entry gains the new provider as an alias:
|
|
174
|
+
* requests naming either id rotate across all serving keys.
|
|
175
|
+
*
|
|
176
|
+
* Provider-only models (claude-*, unique vendor ids) are added as
|
|
177
|
+
* `<id>@<providerId>` suffixed entries.
|
|
178
|
+
*
|
|
179
|
+
* Only providers the user actually has keys for are merged: exposing
|
|
180
|
+
* provider-only models without a usable key would create broken entries in
|
|
181
|
+
* harness configs.
|
|
182
|
+
*/
|
|
183
|
+
async function appendProviderCatalogModels(map, existingIds, config) {
|
|
184
|
+
const configured = new Set((config.keys ?? []).map((k) => inferProvider(k)));
|
|
185
|
+
const providers = allProviders().filter((p) => configured.has(p.id) && typeof p.fetchCatalog === 'function');
|
|
186
|
+
// Bare-name index of what's already in the map (from the primary upstream)
|
|
187
|
+
const byBareName = new Map();
|
|
188
|
+
for (const id of existingIds) {
|
|
189
|
+
const bare = bareModelName(id);
|
|
190
|
+
if (!byBareName.has(bare))
|
|
191
|
+
byBareName.set(bare, id);
|
|
192
|
+
}
|
|
193
|
+
await Promise.all(providers.map(async (provider) => {
|
|
194
|
+
const catalog = await provider.fetchCatalog();
|
|
195
|
+
if (!catalog)
|
|
196
|
+
return;
|
|
197
|
+
for (const entry of catalog) {
|
|
198
|
+
const rawId = typeof entry.id === 'string' ? entry.id : '';
|
|
199
|
+
if (!rawId)
|
|
200
|
+
continue;
|
|
201
|
+
const bare = bareModelName(rawId);
|
|
202
|
+
const existingId = byBareName.get(bare);
|
|
203
|
+
if (existingId) {
|
|
204
|
+
// Same model served by both providers → alias it onto the existing
|
|
205
|
+
// entry so either id routes to the full serving key pool.
|
|
206
|
+
const entryRec = map[existingId];
|
|
207
|
+
const aliases = Array.isArray(entryRec?.aliases)
|
|
208
|
+
? entryRec.aliases
|
|
209
|
+
: [];
|
|
210
|
+
if (!aliases.includes(rawId))
|
|
211
|
+
aliases.push(rawId);
|
|
212
|
+
entryRec.aliases = aliases;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (provider.modelAffinity(rawId) !== 'yes')
|
|
216
|
+
continue; // not uniquely claimable
|
|
217
|
+
const suffixedId = `${rawId}${PROVIDER_MODEL_SUFFIX}${provider.id}`;
|
|
218
|
+
if (existingIds.has(suffixedId))
|
|
219
|
+
continue;
|
|
220
|
+
existingIds.add(suffixedId);
|
|
221
|
+
byBareName.set(bare, suffixedId);
|
|
222
|
+
map[suffixedId] = providerCatalogToCanonical(suffixedId, entry, countServingKeys(config, bare, provider.id));
|
|
223
|
+
}
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
52
226
|
/**
|
|
53
227
|
* Build the canonical model map (id → opencode-shaped entry).
|
|
54
228
|
*
|
|
55
229
|
* Live path (config provided, not offline): fetch upstream model IDs and
|
|
56
|
-
* enrich each with models.dev metadata via buildMinimalStub
|
|
57
|
-
*
|
|
230
|
+
* enrich each with models.dev metadata via buildMinimalStub, then append
|
|
231
|
+
* provider-specific catalog models (@commandcode etc.). Falls back to the
|
|
232
|
+
* bundled models.json when the upstream fetch yields nothing.
|
|
58
233
|
* Offline path (or no config): bundled models.json only.
|
|
59
234
|
*/
|
|
60
235
|
export async function buildCanonicalModels(config, opts) {
|
|
@@ -71,8 +246,23 @@ export async function buildCanonicalModels(config, opts) {
|
|
|
71
246
|
const meta = await fetchModelsDevMetadata();
|
|
72
247
|
if (ids && ids.length > 0) {
|
|
73
248
|
const map = {};
|
|
249
|
+
const seen = new Set();
|
|
74
250
|
for (const id of ids) {
|
|
75
251
|
map[id] = buildMinimalStub(id, meta ?? undefined);
|
|
252
|
+
seen.add(id);
|
|
253
|
+
}
|
|
254
|
+
await appendProviderCatalogModels(map, seen, config);
|
|
255
|
+
// Rotation-width annotation on the primary (opencode-go) entries:
|
|
256
|
+
// "DeepSeek V4 Flash (3)" = 3 keys can serve this model.
|
|
257
|
+
for (const [id, entry] of Object.entries(map)) {
|
|
258
|
+
if (id.includes(PROVIDER_MODEL_SUFFIX))
|
|
259
|
+
continue;
|
|
260
|
+
if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
|
|
261
|
+
const rec = entry;
|
|
262
|
+
if (typeof rec.name === 'string') {
|
|
263
|
+
rec.name = annotateName(rec.name, countServingKeys(config, bareModelName(id), 'opencode-go'));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
76
266
|
}
|
|
77
267
|
return map;
|
|
78
268
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harness-models.js","sourceRoot":"","sources":["../../src/cli/harness-models.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"harness-models.js","sourceRoot":"","sources":["../../src/cli/harness-models.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { ProxyConfig } from '../config.js';
|
|
11
11
|
import { type OpencodeConfigResult } from './opencode-config.js';
|
|
12
|
-
export type HarnessId = 'opencode' | 'pi' | 'omp';
|
|
12
|
+
export type HarnessId = 'opencode' | 'pi' | 'omp' | 'dsh';
|
|
13
13
|
export interface HarnessSyncResult {
|
|
14
14
|
harness: HarnessId;
|
|
15
15
|
result: OpencodeConfigResult;
|
|
@@ -28,19 +28,36 @@ export declare function getHarnessSettingsPath(): string;
|
|
|
28
28
|
export declare function readHarnessSettings(): HarnessId[];
|
|
29
29
|
/** Write the enabled harness ids (replaces any previous selection). */
|
|
30
30
|
export declare function writeHarnessSettings(ids: HarnessId[]): void;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
export interface HarnessCommandArgs {
|
|
32
|
+
add: HarnessId[];
|
|
33
|
+
remove: HarnessId[];
|
|
34
|
+
clear: boolean;
|
|
34
35
|
errors: string[];
|
|
35
|
-
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Parse `configharness` CLI args into an add/remove/clear operation.
|
|
39
|
+
*
|
|
40
|
+
* Harness names before `--remove` are added to the selection, names after it
|
|
41
|
+
* are removed; `--clear` alone empties the selection. Unknown names land in
|
|
42
|
+
* `errors` (the caller reports them and exits non-zero).
|
|
43
|
+
*/
|
|
44
|
+
export declare function parseHarnessCommandArgs(args: string[]): HarnessCommandArgs;
|
|
45
|
+
/**
|
|
46
|
+
* Apply an add/remove/clear operation to the harness selection.
|
|
47
|
+
*
|
|
48
|
+
* Additive by design: names are merged into the current selection, so
|
|
49
|
+
* `configharness omp` followed by `configharness pi` enables both. A missing
|
|
50
|
+
* settings file starts from an empty selection on the first explicit command
|
|
51
|
+
* (the implicit opencode default only applies while nothing is configured).
|
|
52
|
+
*
|
|
53
|
+
* @returns the resulting selection after the write.
|
|
54
|
+
*/
|
|
55
|
+
export declare function updateHarnessSettings(op: Omit<HarnessCommandArgs, 'errors'>): HarnessId[];
|
|
36
56
|
/**
|
|
37
57
|
* Sync models to every enabled harness (opt-in via the settings file).
|
|
38
58
|
*
|
|
39
59
|
* @param config — Proxy config; required for the live (upstream) opencode
|
|
40
|
-
* sync. When undefined,
|
|
41
|
-
* @param opts.offline — Skip network: use bundled models.json everywhere.
|
|
60
|
+
* sync. When undefined, harnesses fall back to the bundled model list.
|
|
42
61
|
* @returns One result per enabled harness, in the order opencode, pi, omp.
|
|
43
62
|
*/
|
|
44
|
-
export declare function syncModelsInAllHarnesses(config?: ProxyConfig
|
|
45
|
-
offline?: boolean;
|
|
46
|
-
}): Promise<HarnessSyncResult[]>;
|
|
63
|
+
export declare function syncModelsInAllHarnesses(config?: ProxyConfig): Promise<HarnessSyncResult[]>;
|
package/dist/cli/harness-sync.js
CHANGED
|
@@ -14,8 +14,9 @@ import { syncModelsToOpencodeConfig } from './opencode-config.js';
|
|
|
14
14
|
import { syncOpencodeModelsWithUpstream } from '../models-sync.js';
|
|
15
15
|
import { syncModelsToPiConfig } from './pi-config.js';
|
|
16
16
|
import { syncModelsToOmpConfig } from './omp-config.js';
|
|
17
|
+
import { syncModelsToDshConfig } from './dsh-config.js';
|
|
17
18
|
import { buildCanonicalModels } from './harness-models.js';
|
|
18
|
-
const VALID_HARNESS_IDS = ['opencode', 'pi', 'omp'];
|
|
19
|
+
const VALID_HARNESS_IDS = ['opencode', 'pi', 'omp', 'dsh'];
|
|
19
20
|
/** Path to the harness-selection setting: ~/.config/saros/harnesses.json */
|
|
20
21
|
export function getHarnessSettingsPath() {
|
|
21
22
|
return join(homedir(), '.config', 'saros', 'harnesses.json');
|
|
@@ -55,46 +56,96 @@ const HARNESS_ALIASES = {
|
|
|
55
56
|
pi: 'pi',
|
|
56
57
|
oc: 'opencode',
|
|
57
58
|
opencode: 'opencode',
|
|
59
|
+
dsh: 'dsh',
|
|
60
|
+
deepseek: 'dsh',
|
|
58
61
|
};
|
|
59
|
-
/**
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Parse `configharness` CLI args into an add/remove/clear operation.
|
|
64
|
+
*
|
|
65
|
+
* Harness names before `--remove` are added to the selection, names after it
|
|
66
|
+
* are removed; `--clear` alone empties the selection. Unknown names land in
|
|
67
|
+
* `errors` (the caller reports them and exits non-zero).
|
|
68
|
+
*/
|
|
69
|
+
export function parseHarnessCommandArgs(args) {
|
|
70
|
+
const add = [];
|
|
71
|
+
const remove = [];
|
|
62
72
|
const errors = [];
|
|
73
|
+
let clear = false;
|
|
74
|
+
let removing = false;
|
|
63
75
|
for (const arg of args) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
ids.push(mapped);
|
|
76
|
+
if (arg === '--clear') {
|
|
77
|
+
clear = true;
|
|
78
|
+
continue;
|
|
68
79
|
}
|
|
69
|
-
|
|
80
|
+
if (arg === '--remove') {
|
|
81
|
+
removing = true;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const mapped = HARNESS_ALIASES[arg.toLowerCase()];
|
|
85
|
+
if (!mapped) {
|
|
70
86
|
errors.push(arg);
|
|
87
|
+
continue;
|
|
71
88
|
}
|
|
89
|
+
const target = removing ? remove : add;
|
|
90
|
+
if (!target.includes(mapped))
|
|
91
|
+
target.push(mapped);
|
|
72
92
|
}
|
|
73
|
-
return {
|
|
93
|
+
return { add, remove, clear, errors };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Apply an add/remove/clear operation to the harness selection.
|
|
97
|
+
*
|
|
98
|
+
* Additive by design: names are merged into the current selection, so
|
|
99
|
+
* `configharness omp` followed by `configharness pi` enables both. A missing
|
|
100
|
+
* settings file starts from an empty selection on the first explicit command
|
|
101
|
+
* (the implicit opencode default only applies while nothing is configured).
|
|
102
|
+
*
|
|
103
|
+
* @returns the resulting selection after the write.
|
|
104
|
+
*/
|
|
105
|
+
export function updateHarnessSettings(op) {
|
|
106
|
+
if (op.clear) {
|
|
107
|
+
writeHarnessSettings([]);
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
const fileExists = existsSync(getHarnessSettingsPath());
|
|
111
|
+
if (!fileExists && op.add.length === 0) {
|
|
112
|
+
// Nothing configured yet and nothing to add — keep the implicit
|
|
113
|
+
// opencode default instead of writing an explicit empty list.
|
|
114
|
+
return readHarnessSettings();
|
|
115
|
+
}
|
|
116
|
+
const base = fileExists ? readHarnessSettings() : [];
|
|
117
|
+
const next = base.filter((id) => !op.remove.includes(id));
|
|
118
|
+
for (const id of op.add) {
|
|
119
|
+
if (!next.includes(id))
|
|
120
|
+
next.push(id);
|
|
121
|
+
}
|
|
122
|
+
writeHarnessSettings(next);
|
|
123
|
+
return next;
|
|
74
124
|
}
|
|
75
125
|
/**
|
|
76
126
|
* Sync models to every enabled harness (opt-in via the settings file).
|
|
77
127
|
*
|
|
78
128
|
* @param config — Proxy config; required for the live (upstream) opencode
|
|
79
|
-
* sync. When undefined,
|
|
80
|
-
* @param opts.offline — Skip network: use bundled models.json everywhere.
|
|
129
|
+
* sync. When undefined, harnesses fall back to the bundled model list.
|
|
81
130
|
* @returns One result per enabled harness, in the order opencode, pi, omp.
|
|
82
131
|
*/
|
|
83
|
-
export async function syncModelsInAllHarnesses(config
|
|
132
|
+
export async function syncModelsInAllHarnesses(config) {
|
|
84
133
|
const enabled = readHarnessSettings();
|
|
85
134
|
if (enabled.length === 0)
|
|
86
135
|
return [];
|
|
87
136
|
const results = [];
|
|
88
137
|
if (enabled.includes('opencode')) {
|
|
89
|
-
if (
|
|
138
|
+
if (!config) {
|
|
90
139
|
results.push({ harness: 'opencode', result: syncModelsToOpencodeConfig() });
|
|
91
140
|
}
|
|
92
141
|
else {
|
|
93
142
|
results.push({ harness: 'opencode', result: await syncOpencodeModelsWithUpstream(config) });
|
|
94
143
|
}
|
|
95
144
|
}
|
|
96
|
-
if (enabled.includes('pi') ||
|
|
97
|
-
|
|
145
|
+
if (enabled.includes('pi') ||
|
|
146
|
+
enabled.includes('omp') ||
|
|
147
|
+
enabled.includes('dsh')) {
|
|
148
|
+
const modelsMap = await buildCanonicalModels(config);
|
|
98
149
|
const port = config?.port ?? 3000;
|
|
99
150
|
if (enabled.includes('pi')) {
|
|
100
151
|
results.push({ harness: 'pi', result: syncModelsToPiConfig(modelsMap, port) });
|
|
@@ -102,6 +153,9 @@ export async function syncModelsInAllHarnesses(config, opts = {}) {
|
|
|
102
153
|
if (enabled.includes('omp')) {
|
|
103
154
|
results.push({ harness: 'omp', result: syncModelsToOmpConfig(modelsMap, port) });
|
|
104
155
|
}
|
|
156
|
+
if (enabled.includes('dsh')) {
|
|
157
|
+
results.push({ harness: 'dsh', result: syncModelsToDshConfig(modelsMap, port) });
|
|
158
|
+
}
|
|
105
159
|
}
|
|
106
160
|
return results;
|
|
107
161
|
}
|