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 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 returned by `/v1/models`; the API key remains in
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`. The command fetches the active
198
- gateway's authenticated `/v1/models` catalog and falls back to the models
199
- packaged with the CLI when discovery is unavailable:
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.error) {
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 === 'gateway' &&
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, { selectedModel, error: catalog.error });
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
- * Live discovery is best-effort: launches should keep working with the
5
- * packaged registry when the gateway is offline or does not expose /v1/models.
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 = `${baseUrl.trim().replace(/\/+$/, '')}/v1/models`;
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
- const models = normalizeModelIds(payload.data.map((model) => model?.id));
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 fetchGatewayModels({ baseUrl, apiKey, fetchImpl, timeoutMs });
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
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.error) {
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": "chat-completions",
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/chat/completions",
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 chat_url
283
+ local messages_url
284
284
  case "$base_url" in
285
- */v1/chat/completions) chat_url="$base_url" ;;
286
- */v1) chat_url="${base_url}/chat/completions" ;;
287
- *) chat_url="${base_url}/v1/chat/completions" ;;
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 "$chat_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: "chat-completions",
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "subconscious-cli",
3
- "version": "4.0.11",
3
+ "version": "4.0.13",
4
4
  "description": "CLI for Subconscious — run Claude Code, Codex, OpenCode, DeepSeek Harness, Cursor, Copilot, and Pi",
5
5
  "bin": {
6
6
  "subc": "bin/cli.js"