subconscious-cli 4.0.11 → 4.0.13
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 +8 -4
- package/bin/agents.js +5 -3
- package/bin/cli.js +6 -1
- package/bin/models.js +87 -25
- package/bin/native/subc-tui-darwin-amd64 +0 -0
- package/bin/native/subc-tui-darwin-arm64 +0 -0
- package/bin/native/subc-tui-linux-amd64 +0 -0
- package/bin/native/subc-tui-linux-arm64 +0 -0
- package/bin/native/subc-tui-windows-amd64.exe +0 -0
- package/bin/native/subc-tui-windows-arm64.exe +0 -0
- package/bin/profiles.js +4 -1
- package/bin/runbook/copilot/install.sh +10 -8
- package/bin/tui.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -122,13 +122,16 @@ saved to the selected profile.
|
|
|
122
122
|
Cursor still requires enabling its OpenAI API Key Override in Cursor Settings.
|
|
123
123
|
Copilot requires entering the custom endpoint key once through VS Code's
|
|
124
124
|
Manage Language Models UI. The install scripts print the relevant next steps.
|
|
125
|
+
Its Custom Endpoint provider uses the Messages API at `/v1/messages` by
|
|
126
|
+
default; rerun `subc copilot install` to migrate an older Chat Completions entry.
|
|
125
127
|
|
|
126
128
|
The runbook scripts require Bash. Cursor, Copilot, Pi, and Codex hook merge
|
|
127
129
|
also require `jq`; Cursor and Copilot also require `curl`.
|
|
128
130
|
|
|
129
131
|
`subc dsh` defaults to the DeepSeek Harness Web UI. It injects an ephemeral
|
|
130
132
|
Cordis overlay containing the active gateway URL, selected model, token limits,
|
|
131
|
-
client header, and every model
|
|
133
|
+
client header, and every model from the live catalog (`/v1/models/available`
|
|
134
|
+
when a profile key is present, otherwise public `/v1/models`); the API key remains in
|
|
132
135
|
the process environment and is never written into the overlay. The temporary
|
|
133
136
|
file is removed when Harness exits, and existing `$DSH_HOME` settings are not
|
|
134
137
|
rewritten. Run a one-shot task with `subc dsh headless "PROMPT"`.
|
|
@@ -194,9 +197,10 @@ values. A command-line `--model` override has the highest model precedence.
|
|
|
194
197
|
|
|
195
198
|
## Models and endpoint overrides
|
|
196
199
|
|
|
197
|
-
List the available models with `subc models`.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
+
List the available models with `subc models`. When the selected profile has an
|
|
201
|
+
API key, the command fetches the key-scoped `/v1/models/available` catalog. If
|
|
202
|
+
that request fails, it falls back to the public `/v1/models` fleet list, then
|
|
203
|
+
to the models packaged with the CLI:
|
|
200
204
|
|
|
201
205
|
```text
|
|
202
206
|
subconscious/glm-5.2 (default)
|
package/bin/agents.js
CHANGED
|
@@ -21,7 +21,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
21
21
|
import { c } from './colors.js';
|
|
22
22
|
import { getApiKey } from './auth.js';
|
|
23
23
|
import { profileSettingsForAgent, resolvedProfileValues } from './profiles.js';
|
|
24
|
-
import { resolveModelCatalog } from './models.js';
|
|
24
|
+
import { isLiveModelSource, PUBLIC_CATALOG_FALLBACK_MESSAGE, resolveModelCatalog } from './models.js';
|
|
25
25
|
|
|
26
26
|
// --- Registry (single source of truth, generated copy shipped in the package).
|
|
27
27
|
const registry = JSON.parse(
|
|
@@ -709,7 +709,9 @@ async function resolvedModelsForLaunch(profile, apiKey, selectedModel) {
|
|
|
709
709
|
selectedModel,
|
|
710
710
|
fallbackModels: PACKAGED_MODELS,
|
|
711
711
|
});
|
|
712
|
-
if (catalog.
|
|
712
|
+
if (catalog.source === 'public' && apiKey) {
|
|
713
|
+
console.error(` ${c.dim}${PUBLIC_CATALOG_FALLBACK_MESSAGE}${c.reset}\n`);
|
|
714
|
+
} else if (catalog.error) {
|
|
713
715
|
console.error(
|
|
714
716
|
` ${c.yellow}Could not fetch the live model catalog; using packaged defaults.${c.reset}`,
|
|
715
717
|
);
|
|
@@ -720,7 +722,7 @@ async function resolvedModelsForLaunch(profile, apiKey, selectedModel) {
|
|
|
720
722
|
|
|
721
723
|
export function selectLaunchModel(requestedModel, modelSource, catalog) {
|
|
722
724
|
const useLiveDefault =
|
|
723
|
-
catalog.source
|
|
725
|
+
isLiveModelSource(catalog.source) &&
|
|
724
726
|
catalog.models.length > 0 &&
|
|
725
727
|
!catalog.models.includes(requestedModel) &&
|
|
726
728
|
(modelSource === 'profile' || modelSource === 'default');
|
package/bin/cli.js
CHANGED
|
@@ -323,7 +323,12 @@ async function main() {
|
|
|
323
323
|
selectedModel,
|
|
324
324
|
fallbackModels: PACKAGED_MODELS,
|
|
325
325
|
});
|
|
326
|
-
modelsCommand(catalog.models, {
|
|
326
|
+
modelsCommand(catalog.models, {
|
|
327
|
+
selectedModel,
|
|
328
|
+
error: catalog.error,
|
|
329
|
+
source: catalog.source,
|
|
330
|
+
hasApiKey: Boolean(auth?.key),
|
|
331
|
+
});
|
|
327
332
|
return;
|
|
328
333
|
}
|
|
329
334
|
|
package/bin/models.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resolve the model catalog exposed by an OpenAI-compatible gateway.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* When a profile (or agent) API key is present, prefer the key-scoped
|
|
5
|
+
* /v1/models/available list. If that request fails, fall back to the public
|
|
6
|
+
* /v1/models fleet catalog. Launches keep working with the packaged registry
|
|
7
|
+
* when the gateway is offline or returns nothing usable.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
const MODEL_ID_PATTERN = /^[-A-Za-z0-9._:/+]+$/;
|
|
9
11
|
export const DEFAULT_MODEL_FETCH_TIMEOUT_MS = 3000;
|
|
12
|
+
export const AVAILABLE_MODELS_PATH = '/v1/models/available';
|
|
13
|
+
export const PUBLIC_MODELS_PATH = '/v1/models';
|
|
14
|
+
export const PUBLIC_CATALOG_FALLBACK_MESSAGE =
|
|
15
|
+
'Provisioned catalog unavailable; showing the public model list.';
|
|
16
|
+
|
|
17
|
+
export function isLiveModelSource(source) {
|
|
18
|
+
return source === 'available' || source === 'public';
|
|
19
|
+
}
|
|
10
20
|
|
|
11
21
|
export function normalizeModelIds(modelIds = [], selectedModel) {
|
|
12
22
|
const models = [];
|
|
@@ -27,19 +37,37 @@ export function normalizeModelIds(modelIds = [], selectedModel) {
|
|
|
27
37
|
return models;
|
|
28
38
|
}
|
|
29
39
|
|
|
40
|
+
function gatewayOrigin(baseUrl) {
|
|
41
|
+
if (!baseUrl?.trim()) throw new Error('Gateway URL is not configured');
|
|
42
|
+
return baseUrl.trim().replace(/\/+$/, '');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function modelsEndpoint(baseUrl, path) {
|
|
46
|
+
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
47
|
+
return `${gatewayOrigin(baseUrl)}${normalizedPath}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
30
50
|
export async function fetchGatewayModels({
|
|
31
51
|
baseUrl,
|
|
32
52
|
apiKey,
|
|
53
|
+
path = PUBLIC_MODELS_PATH,
|
|
33
54
|
fetchImpl = globalThis.fetch,
|
|
34
55
|
timeoutMs = DEFAULT_MODEL_FETCH_TIMEOUT_MS,
|
|
35
56
|
}) {
|
|
36
|
-
if (!baseUrl?.trim()) throw new Error('Gateway URL is not configured');
|
|
37
|
-
if (!apiKey?.trim()) throw new Error('No API key is available for model discovery');
|
|
38
57
|
if (typeof fetchImpl !== 'function') {
|
|
39
58
|
throw new Error('This Node.js version does not support fetch');
|
|
40
59
|
}
|
|
41
60
|
|
|
42
|
-
const endpoint =
|
|
61
|
+
const endpoint = modelsEndpoint(baseUrl, path);
|
|
62
|
+
const headers = {
|
|
63
|
+
Accept: 'application/json',
|
|
64
|
+
'Cache-Control': 'no-cache, no-store',
|
|
65
|
+
Pragma: 'no-cache',
|
|
66
|
+
};
|
|
67
|
+
if (apiKey?.trim()) {
|
|
68
|
+
headers.Authorization = `Bearer ${apiKey.trim()}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
43
71
|
const controller = new AbortController();
|
|
44
72
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
45
73
|
timeout.unref?.();
|
|
@@ -47,12 +75,7 @@ export async function fetchGatewayModels({
|
|
|
47
75
|
try {
|
|
48
76
|
const response = await fetchImpl(endpoint, {
|
|
49
77
|
method: 'GET',
|
|
50
|
-
headers
|
|
51
|
-
Accept: 'application/json',
|
|
52
|
-
Authorization: `Bearer ${apiKey.trim()}`,
|
|
53
|
-
'Cache-Control': 'no-cache, no-store',
|
|
54
|
-
Pragma: 'no-cache',
|
|
55
|
-
},
|
|
78
|
+
headers,
|
|
56
79
|
cache: 'no-store',
|
|
57
80
|
signal: controller.signal,
|
|
58
81
|
});
|
|
@@ -66,9 +89,7 @@ export async function fetchGatewayModels({
|
|
|
66
89
|
throw new Error('Model discovery returned an invalid response');
|
|
67
90
|
}
|
|
68
91
|
|
|
69
|
-
|
|
70
|
-
if (!models.length) throw new Error('Model discovery returned no usable model IDs');
|
|
71
|
-
return models;
|
|
92
|
+
return normalizeModelIds(payload.data.map((model) => model?.id));
|
|
72
93
|
} catch (error) {
|
|
73
94
|
if (controller.signal.aborted) {
|
|
74
95
|
throw new Error(`Model discovery timed out after ${timeoutMs}ms`);
|
|
@@ -79,6 +100,33 @@ export async function fetchGatewayModels({
|
|
|
79
100
|
}
|
|
80
101
|
}
|
|
81
102
|
|
|
103
|
+
async function fetchPublicModels({ baseUrl, fetchImpl, timeoutMs }) {
|
|
104
|
+
const models = await fetchGatewayModels({
|
|
105
|
+
baseUrl,
|
|
106
|
+
path: PUBLIC_MODELS_PATH,
|
|
107
|
+
fetchImpl,
|
|
108
|
+
timeoutMs,
|
|
109
|
+
});
|
|
110
|
+
if (!models.length) throw new Error('Model discovery returned no usable model IDs');
|
|
111
|
+
return models;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function packagedCatalog(selectedModel, fallbackModels, error) {
|
|
115
|
+
return {
|
|
116
|
+
models: normalizeModelIds([selectedModel, ...fallbackModels], selectedModel),
|
|
117
|
+
source: 'packaged',
|
|
118
|
+
error,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function liveCatalog(models, selectedModel, source) {
|
|
123
|
+
return {
|
|
124
|
+
models: normalizeModelIds(models, selectedModel),
|
|
125
|
+
source,
|
|
126
|
+
error: null,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
82
130
|
export async function resolveModelCatalog({
|
|
83
131
|
baseUrl,
|
|
84
132
|
apiKey,
|
|
@@ -87,18 +135,32 @@ export async function resolveModelCatalog({
|
|
|
87
135
|
fetchImpl = globalThis.fetch,
|
|
88
136
|
timeoutMs = DEFAULT_MODEL_FETCH_TIMEOUT_MS,
|
|
89
137
|
}) {
|
|
138
|
+
const key = apiKey?.trim() || '';
|
|
139
|
+
|
|
140
|
+
if (key) {
|
|
141
|
+
try {
|
|
142
|
+
const discovered = await fetchGatewayModels({
|
|
143
|
+
baseUrl,
|
|
144
|
+
apiKey: key,
|
|
145
|
+
path: AVAILABLE_MODELS_PATH,
|
|
146
|
+
fetchImpl,
|
|
147
|
+
timeoutMs,
|
|
148
|
+
});
|
|
149
|
+
return liveCatalog(discovered, selectedModel, 'available');
|
|
150
|
+
} catch (availableError) {
|
|
151
|
+
try {
|
|
152
|
+
const discovered = await fetchPublicModels({ baseUrl, fetchImpl, timeoutMs });
|
|
153
|
+
return liveCatalog(discovered, selectedModel, 'public');
|
|
154
|
+
} catch {
|
|
155
|
+
return packagedCatalog(selectedModel, fallbackModels, availableError);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
90
160
|
try {
|
|
91
|
-
const discovered = await
|
|
92
|
-
return
|
|
93
|
-
models: normalizeModelIds(discovered, selectedModel),
|
|
94
|
-
source: 'gateway',
|
|
95
|
-
error: null,
|
|
96
|
-
};
|
|
161
|
+
const discovered = await fetchPublicModels({ baseUrl, fetchImpl, timeoutMs });
|
|
162
|
+
return liveCatalog(discovered, selectedModel, 'public');
|
|
97
163
|
} catch (error) {
|
|
98
|
-
return
|
|
99
|
-
models: normalizeModelIds([selectedModel, ...fallbackModels], selectedModel),
|
|
100
|
-
source: 'packaged',
|
|
101
|
-
error,
|
|
102
|
-
};
|
|
164
|
+
return packagedCatalog(selectedModel, fallbackModels, error);
|
|
103
165
|
}
|
|
104
166
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/bin/profiles.js
CHANGED
|
@@ -12,6 +12,7 @@ import os from 'node:os';
|
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { readFileSync } from 'node:fs';
|
|
14
14
|
import { c } from './colors.js';
|
|
15
|
+
import { PUBLIC_CATALOG_FALLBACK_MESSAGE } from './models.js';
|
|
15
16
|
|
|
16
17
|
const registry = JSON.parse(
|
|
17
18
|
readFileSync(new URL('./registry.generated.json', import.meta.url), 'utf-8'),
|
|
@@ -818,7 +819,9 @@ export function modelsCommand(models = SUPPORTED_MODELS, options = {}) {
|
|
|
818
819
|
const suffix = model === selectedModel ? ` ${c.dim}(default)${c.reset}` : '';
|
|
819
820
|
console.log(` ${c.cyan}${model}${c.reset}${suffix}`);
|
|
820
821
|
}
|
|
821
|
-
if (options.
|
|
822
|
+
if (options.source === 'public' && options.hasApiKey) {
|
|
823
|
+
console.error(`\n ${c.dim}${PUBLIC_CATALOG_FALLBACK_MESSAGE}${c.reset}`);
|
|
824
|
+
} else if (options.error) {
|
|
822
825
|
console.error(
|
|
823
826
|
`\n ${c.yellow}Could not fetch the live model catalog; showing packaged defaults.${c.reset}`,
|
|
824
827
|
);
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
# "name": "Subconscious Gateway",
|
|
31
31
|
# "vendor": "customendpoint",
|
|
32
32
|
# "apiKey": "${input:chat.lm.secret.subconscious-gateway}",
|
|
33
|
-
# "apiType": "
|
|
33
|
+
# "apiType": "messages",
|
|
34
34
|
# "models": [
|
|
35
35
|
# {
|
|
36
36
|
# "id": "subconscious/glm-5.2",
|
|
37
37
|
# "name": "Subconscious GLM 5.2",
|
|
38
|
-
# "url": "https://your-gateway.example/v1/
|
|
38
|
+
# "url": "https://your-gateway.example/v1/messages",
|
|
39
39
|
# "toolCalling": true,
|
|
40
40
|
# "vision": false,
|
|
41
41
|
# "maxInputTokens": 5000000,
|
|
@@ -280,11 +280,13 @@ write_config() {
|
|
|
280
280
|
local user_dir="$1"
|
|
281
281
|
local models_json="${user_dir}/chatLanguageModels.json"
|
|
282
282
|
local base_url="${GATEWAY_URL%/}"
|
|
283
|
-
local
|
|
283
|
+
local messages_url
|
|
284
284
|
case "$base_url" in
|
|
285
|
-
*/v1/
|
|
286
|
-
*/v1)
|
|
287
|
-
|
|
285
|
+
*/v1/messages) messages_url="$base_url" ;;
|
|
286
|
+
*/v1/chat/completions) messages_url="${base_url%/chat/completions}/messages" ;;
|
|
287
|
+
*/v1/responses) messages_url="${base_url%/responses}/messages" ;;
|
|
288
|
+
*/v1) messages_url="${base_url}/messages" ;;
|
|
289
|
+
*) messages_url="${base_url}/v1/messages" ;;
|
|
288
290
|
esac
|
|
289
291
|
|
|
290
292
|
mkdir -p "$user_dir"
|
|
@@ -297,7 +299,7 @@ write_config() {
|
|
|
297
299
|
--argjson models "$provider_models" \
|
|
298
300
|
--arg modelId "$model_id" \
|
|
299
301
|
--arg modelName "Subconscious ${model_id}" \
|
|
300
|
-
--arg url "$
|
|
302
|
+
--arg url "$messages_url" \
|
|
301
303
|
--argjson maxIn "$MAX_INPUT_TOKENS" \
|
|
302
304
|
--argjson maxOut "$MAX_OUTPUT_TOKENS" \
|
|
303
305
|
'$models + [{
|
|
@@ -323,7 +325,7 @@ write_config() {
|
|
|
323
325
|
name: $name,
|
|
324
326
|
vendor: "customendpoint",
|
|
325
327
|
apiKey: $apiKeyRef,
|
|
326
|
-
apiType: "
|
|
328
|
+
apiType: "messages",
|
|
327
329
|
models: $models
|
|
328
330
|
}')
|
|
329
331
|
|
package/bin/tui.js
CHANGED
|
@@ -140,6 +140,7 @@ export async function createTuiState(profileName = DEFAULT_PROFILE, options = {}
|
|
|
140
140
|
RUNBOOK_DEFAULTS.GATEWAY_URL,
|
|
141
141
|
gatewayOverridden: Boolean(process.env.SUBCONSCIOUS_BASE_URL?.trim()),
|
|
142
142
|
modelError: catalog.error?.message || '',
|
|
143
|
+
modelSource: catalog.source,
|
|
143
144
|
agents: agentList().map((agent) => ({
|
|
144
145
|
command: agent.alias,
|
|
145
146
|
name: agent.name,
|