subconscious-cli 4.1.2 → 4.1.4

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/bin/agents.js CHANGED
@@ -32,6 +32,15 @@ import { compareVersions } from './update-check.js';
32
32
 
33
33
  export const MIN_CLAUDE_CODE_VERSION = '2.1.242';
34
34
 
35
+ // First Codex that honours `multi_agent_version: "v2"` in the model catalog,
36
+ // found by installing each release and reading what it advertises: 0.142.5 and
37
+ // below offer no subagent namespace at all, 0.143.0 offers `collaboration`.
38
+ //
39
+ // Below this the catalog field is not rejected, it is ignored - the run simply
40
+ // has no subagents and nothing says why. That silence is the reason for a
41
+ // version gate rather than a note in the docs.
42
+ export const MIN_CODEX_VERSION = '0.143.0';
43
+
35
44
  // --- Registry (single source of truth, generated copy shipped in the package).
36
45
  const registry = JSON.parse(
37
46
  readFileSync(new URL('./registry.generated.json', import.meta.url), 'utf-8'),
@@ -193,10 +202,10 @@ const AGENT_HELP = {
193
202
  codex: {
194
203
  usage: 'subc [-p NAME] codex [help|install|status|uninstall] [Codex arguments...]',
195
204
  behavior:
196
- 'Launches Codex with a temporary Subconscious provider catalog. Compaction hooks are merged into ~/.codex/hooks.json and removed with subc codex uninstall.',
205
+ 'Launches Codex with a temporary Subconscious provider catalog. Compaction hooks are merged into ~/.codex/hooks.json on first launch and removed with subc codex uninstall; Codex will not run them until you trust them with /hooks once.',
197
206
  options: [
198
207
  ['help', 'Show this help'],
199
- ['install', 'Install only the Subconscious compaction hooks'],
208
+ ['install', 'Install the Subconscious compaction hooks (then trust with /hooks)'],
200
209
  ['status', 'Inspect the installed compaction hooks'],
201
210
  ['uninstall', 'Remove only the Subconscious Codex hooks'],
202
211
  ['--model MODEL', 'Override the profile model for this launch'],
@@ -205,7 +214,9 @@ const AGENT_HELP = {
205
214
  ['--auto-compact-token-limit N', 'Override the automatic compaction threshold'],
206
215
  ['--reasoning-effort LEVEL', 'Use none, low, medium, high, or max'],
207
216
  ['--external-tools', 'Enable Codex apps/plugins for this launch'],
208
- ['--subagents', 'Use the pinned legacy Codex subagent mode'],
217
+ ['--stream-idle-timeout MS', 'Allow a longer silent think before timing out'],
218
+ ['--max-subagents N', 'Subagents allowed to run at once (default 4)'],
219
+ ['--subagent-effort LEVEL', 'Effort for subagents, separate from the parent'],
209
220
  ['-- ARGS...', 'Pass remaining arguments to Codex'],
210
221
  ],
211
222
  },
@@ -625,6 +636,10 @@ export function parseClaudeVersion(text) {
625
636
  }
626
637
 
627
638
  export function claudeVersionNeedsUpgrade(installed, minimum = MIN_CLAUDE_CODE_VERSION) {
639
+ return versionNeedsUpgrade(installed, minimum);
640
+ }
641
+
642
+ export function versionNeedsUpgrade(installed, minimum) {
628
643
  return Boolean(installed && minimum && compareVersions(installed, minimum) < 0);
629
644
  }
630
645
 
@@ -649,13 +664,20 @@ export function readClaudeVersion(bin, binDir, options = {}) {
649
664
  }
650
665
  }
651
666
 
667
+ const MINIMUM_VERSIONS = {
668
+ 'claude-code': { minimum: MIN_CLAUDE_CODE_VERSION, label: 'Claude Code' },
669
+ // Below this, subagents are silently absent rather than broken.
670
+ codex: { minimum: MIN_CODEX_VERSION, label: 'Codex' },
671
+ };
672
+
652
673
  async function ensureClaudeCompatible(agent, binDir) {
653
- if (agent.id !== 'claude-code') return;
674
+ const requirement = MINIMUM_VERSIONS[agent.id];
675
+ if (!requirement) return;
654
676
  const version = readClaudeVersion(agent.bin, binDir);
655
- if (!claudeVersionNeedsUpgrade(version)) return;
677
+ if (!versionNeedsUpgrade(version, requirement.minimum)) return;
656
678
 
657
679
  console.error(
658
- `\n Minimum supported Claude Code version is ${MIN_CLAUDE_CODE_VERSION}. Your version is ${version}. Upgrade to get the best experience.\n`,
680
+ `\n Minimum supported ${requirement.label} version is ${requirement.minimum}. Your version is ${version}. Upgrade to get the best experience.\n`,
659
681
  );
660
682
  console.error(` Upgrade it with:`);
661
683
  console.error(` ${c.cyan}${agent.install}${c.reset}`);
@@ -0,0 +1,11 @@
1
+ import { readFileSync } from 'node:fs';
2
+
3
+ // The models endpoint currently supplies IDs, not modality metadata. Keep
4
+ // explicit capabilities in the same registry used to generate the Unix lookup.
5
+ const { modelCapabilities = {} } = JSON.parse(
6
+ readFileSync(new URL('./registry.generated.json', import.meta.url), 'utf8'),
7
+ );
8
+
9
+ export function modelSupportsVision(modelId) {
10
+ return Object.hasOwn(modelCapabilities, modelId) && modelCapabilities[modelId].vision === true;
11
+ }
Binary file
Binary file
Binary file
Binary file
package/bin/profiles.js CHANGED
@@ -144,6 +144,28 @@ const SETTINGS = {
144
144
  type: 'integer',
145
145
  min: 1,
146
146
  },
147
+ CODEX_SUBAGENT_REASONING_EFFORT: {
148
+ key: 'CODEX_SUBAGENT_REASONING_EFFORT',
149
+ label: 'Subagent reasoning effort',
150
+ description:
151
+ 'Effort for Codex subagents, set separately from the parent: a marathon model at max thinks for minutes without emitting a token, and a subagent turn is one uninterrupted think, so inheriting max makes every delegated subtask look like a hang.',
152
+ type: 'text',
153
+ },
154
+ CODEX_MULTI_AGENT_VERSION: {
155
+ key: 'CODEX_MULTI_AGENT_VERSION',
156
+ label: 'Codex multi-agent version',
157
+ description:
158
+ 'Which subagent implementation Codex offers: v2 gives the collaboration tools. Leave empty to turn subagents off entirely.',
159
+ type: 'text',
160
+ },
161
+ CODEX_STREAM_IDLE_TIMEOUT_MS: {
162
+ key: 'CODEX_STREAM_IDLE_TIMEOUT_MS',
163
+ label: 'Stream idle timeout (ms)',
164
+ description:
165
+ 'How long Codex waits on a silent stream before giving up. Needs to exceed the longest think the model will take.',
166
+ type: 'integer',
167
+ min: 1,
168
+ },
147
169
  MAX_SUBAGENT_SPAWN_DEPTH: {
148
170
  key: 'MAX_SUBAGENT_SPAWN_DEPTH',
149
171
  label: 'Subagent spawn depth',
@@ -305,6 +327,9 @@ const AGENT_SETTING_KEYS = {
305
327
  'CODEX_AUTO_COMPACT_TOKEN_LIMIT',
306
328
  'CODEX_REASONING_EFFORT',
307
329
  'CODEX_EXTERNAL_TOOLS',
330
+ 'CODEX_STREAM_IDLE_TIMEOUT_MS',
331
+ 'CODEX_MULTI_AGENT_VERSION',
332
+ 'CODEX_SUBAGENT_REASONING_EFFORT',
308
333
  'MAX_CONCURRENT_SUBAGENTS',
309
334
  ],
310
335
  opencode: ['OPENCODE_API_KEY', 'OPENCODE_CONTEXT_LIMIT', 'OPENCODE_OUTPUT_LIMIT'],
@@ -6,13 +6,19 @@
6
6
  "subconscious/glm-5.3-marathon",
7
7
  "subconscious/glm-5.2",
8
8
  "subconscious/tim-qwen3.6-27b",
9
- "subconscious/deepseek-v4-flash-marathon"
9
+ "subconscious/deepseek-v4-flash-marathon",
10
+ "subconscious/deepseek-v4.1-flash-marathon"
10
11
  ],
11
12
  "baseUrl": "https://api.subconscious.dev",
12
13
  "baseUrlV1": "https://api.subconscious.dev/v1",
13
14
  "keyPlaceholder": "your_key",
14
15
  "platformUrl": "https://subconscious.dev/platform"
15
16
  },
17
+ "modelCapabilities": {
18
+ "subconscious/deepseek-v4.1-flash-marathon": {
19
+ "vision": true
20
+ }
21
+ },
16
22
  "agents": [
17
23
  {
18
24
  "id": "subconscious-code",
@@ -161,7 +167,7 @@
161
167
  "uninstall"
162
168
  ]
163
169
  },
164
- "launch": "codex -c model={model} -c model_provider=subconscious -c web_search=disabled -c model_providers.subconscious.name=Subconscious -c model_providers.subconscious.base_url={baseUrlV1} -c model_providers.subconscious.wire_api=responses -c model_providers.subconscious.env_key=SUBCONSCIOUS_API_KEY -c model_providers.subconscious.stream_idle_timeout_ms=300000",
170
+ "launch": "codex -c model={model} -c model_provider=subconscious -c web_search=disabled -c model_providers.subconscious.name=Subconscious -c model_providers.subconscious.base_url={baseUrlV1} -c model_providers.subconscious.wire_api=responses -c model_providers.subconscious.env_key=SUBCONSCIOUS_API_KEY -c model_providers.subconscious.stream_idle_timeout_ms=900000",
165
171
  "env": {
166
172
  "SUBCONSCIOUS_API_KEY": "{apiKey}",
167
173
  "SUBCONSCIOUS_GATEWAY_URL": "{baseUrl}"
@@ -253,6 +259,24 @@
253
259
  "context": 5000000,
254
260
  "output": 65536
255
261
  }
262
+ },
263
+ "subconscious/deepseek-v4.1-flash-marathon": {
264
+ "name": "subconscious/deepseek-v4.1-flash-marathon",
265
+ "tools": true,
266
+ "attachment": true,
267
+ "modalities": {
268
+ "input": [
269
+ "text",
270
+ "image"
271
+ ],
272
+ "output": [
273
+ "text"
274
+ ]
275
+ },
276
+ "limit": {
277
+ "context": 5000000,
278
+ "output": 65536
279
+ }
256
280
  }
257
281
  }
258
282
  }
@@ -61,6 +61,30 @@ EOF
61
61
  chmod 600 "$HOOKS_ENV_FILE"
62
62
  }
63
63
 
64
+ # True when our hooks, hook script and env file are all already in place and
65
+ # current, so there is nothing to write.
66
+ #
67
+ # This matters more than it looks. Codex asks the user to trust a hook before
68
+ # it will run one, and it decides what is "new" from what is on disk. Rewriting
69
+ # an identical hook on every single launch meant the trust prompt never went
70
+ # away and the compaction hooks never actually ran - we were re-introducing
71
+ # ourselves as a stranger several times a day.
72
+ codex_hooks_current() {
73
+ [[ -f "$HOOKS_JSON" ]] || return 1
74
+ codex_hooks_json_valid || return 1
75
+ [[ -x "$HOOK_DST" ]] || return 1
76
+ [[ -f "${HOOK_SRC:-}" ]] || return 1
77
+ cmp -s "$HOOK_SRC" "$HOOK_DST" || return 1
78
+ [[ -f "$HOOKS_ENV_FILE" ]] || return 1
79
+ grep -q "SUBCONSCIOUS_GATEWAY_URL='${GATEWAY_URL%/}'" "$HOOKS_ENV_FILE" || return 1
80
+ grep -q "SUBCONSCIOUS_API_KEY='${API_KEY}'" "$HOOKS_ENV_FILE" || return 1
81
+ # Our entry present under both compaction events, and exactly once each.
82
+ jq -e --arg m "$MARKER" '
83
+ def ours: [ .[]?.hooks[]? | select((.command // "") | tostring | contains($m)) ] | length;
84
+ (.hooks.PreCompact | ours) == 1 and (.hooks.PostCompact | ours) == 1
85
+ ' "$HOOKS_JSON" >/dev/null 2>&1
86
+ }
87
+
64
88
  # codex_ensure_hooks [strict|best-effort]
65
89
  codex_ensure_hooks() {
66
90
  local mode="${1:-strict}"
@@ -91,12 +115,18 @@ codex_ensure_hooks() {
91
115
  if [[ ! -f "$HOOKS_JSON" ]]; then
92
116
  printf '%s\n' '{"hooks":{}}' >"$HOOKS_JSON"
93
117
  fi
118
+ if codex_hooks_current; then
119
+ # Already installed and unchanged. Saying nothing is the point: a message
120
+ # on every launch trains the user to ignore it, and rewriting the file
121
+ # would reset the trust they have already granted.
122
+ return 0
123
+ fi
94
124
  codex_write_hook_script
95
125
  codex_write_hooks_env
96
126
  codex_strip_hook_entries
97
127
  codex_append_hook_groups
98
- echo "Merged Subconscious Codex hooks into $HOOKS_JSON"
99
- echo "Trust new hooks in Codex with /hooks (or --dangerously-bypass-hook-trust)."
128
+ echo "Installed Subconscious compaction hooks in $HOOKS_JSON"
129
+ echo "These do not run until you trust them: type /hooks inside Codex once."
100
130
  }
101
131
 
102
132
  codex_uninstall_hooks() {
@@ -33,6 +33,16 @@ Usage:
33
33
 
34
34
  Merges Subconscious compaction hooks into ~/.codex/hooks.json. Does not write
35
35
  config.toml. Launch Codex with subc codex.
36
+
37
+ The hooks report compaction start and end to the gateway, which is what lets a
38
+ conversation's turns stay correlated across a compaction boundary.
39
+
40
+ Codex will not run a hook until you trust it. After installing, type /hooks
41
+ inside Codex once and approve them; until then they are present but inert.
42
+ Non-interactive runs have nobody to ask, so `codex exec` can only run them with
43
+ --dangerously-bypass-hook-trust, which is yours to opt into and is never passed
44
+ for you. Fleet installs should ship the hooks through Codex's managed
45
+ (MDM) configuration, which is trusted without prompting.
36
46
  EOF
37
47
  }
38
48
 
@@ -6,20 +6,24 @@
6
6
  # The model catalog (needed to suppress the "model metadata not found" warning)
7
7
  # is written to a temp file that is cleaned up on exit.
8
8
  #
9
- # Subagents are OFF by default. Current Codex (>=0.144) wraps subagent tools in a
10
- # `type: "namespace"` wire format that non-OpenAI providers can't resolve, causing
11
- # "unsupported call: spawn_agent" (upstream #32318, #26977). The fix (PR #29602) is
12
- # not yet merged.
9
+ # Subagents are ON by default. The per-model `multi_agent_version` catalog
10
+ # field selects WHICH implementation Codex offers, not whether it offers one:
11
+ # "v2" gives the `collaboration` namespace, and an empty value falls back to
12
+ # the older `multi_agent_v1` namespace rather than turning subagents off.
13
+ # Only `agents.enabled=false` actually disables them, so clearing
14
+ # CODEX_MULTI_AGENT_VERSION does both.
13
15
  #
14
- # To run with subagents, pass --subagents — this runs the pinned legacy
15
- # codex@0.132.0 with multi-agent v1 config (plain tool names) that the model can
16
- # resolve. Requires npx.
16
+ # This needs a gateway that round-trips Codex's namespaced tools. Codex sends
17
+ # them as `type: "namespace"` and resolves an incoming call by namespace plus
18
+ # name, so a gateway that flattens the namespace for the model must restore it
19
+ # on the way back, or every call fails with "unsupported call: spawn_agent".
17
20
  #
18
21
  # Usage:
19
22
  # ./run.sh # uses GATEWAY_URL/API_KEY from ../.env
20
23
  # ./run.sh --context-window 5000000 -- --resume
21
- # ./run.sh --subagents # run codex@0.132.0 with subagents enabled
22
- # ./run.sh --subagents -- --resume # subagents + passthrough args
24
+ # ./run.sh --stream-idle-timeout 1800000 # allow a 30min silent think
25
+ # ./run.sh --max-subagents 12 # raise the concurrent-subagent ceiling
26
+ # ./run.sh --subagent-effort low # cheaper subagents than the parent
23
27
  # ./run.sh --external-tools # include Codex apps/plugins (may exceed gateway tool limits)
24
28
  #
25
29
  # Config: copy ../env.example to ../.env and edit. .env is gitignored.
@@ -31,6 +35,7 @@
31
35
  set -euo pipefail
32
36
 
33
37
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
38
+ source "${SCRIPT_DIR}/../model-capabilities.generated.sh"
34
39
 
35
40
  # Load shared env from SUBC_ENV_FILE, or a sibling .env / env.example.
36
41
  SHARED_ENV="${SUBC_ENV_FILE:-${SCRIPT_DIR}/../.env}"
@@ -40,16 +45,31 @@ if [[ -f "$SHARED_ENV" ]]; then set -a; source "$SHARED_ENV"; set +a; fi
40
45
  GATEWAY_URL="${GATEWAY_URL:-}"
41
46
  API_KEY="${CODEX_API_KEY:-${API_KEY:-}}"
42
47
  MODEL="${MODEL:-subconscious/glm-5.3-marathon}"
43
- MAX_CONCURRENT_SUBAGENTS="${MAX_CONCURRENT_SUBAGENTS:-4}"
44
- SUBAGENTS=false
48
+ MAX_CONCURRENT_SUBAGENTS="${MAX_CONCURRENT_SUBAGENTS:-}"
49
+ # Effort for spawned agents, independent of the parent's. Empty inherits the
50
+ # Codex default. See the note above SUBAGENT_ARGS for why max is a poor choice.
51
+ #
52
+ # Low rather than medium: the gateway rounds medium up to high for GLM, and at
53
+ # high a subagent handed an open-ended task keeps deliberating without ever
54
+ # writing the closing `</think>`. The turn then comes back as one long block of
55
+ # prose with no tool call, so the agent reports success having produced no
56
+ # files. Measured against the worker on one broad task: low closed and called a
57
+ # tool in ~1.5k characters, high ran past 76k and was still going.
58
+ CODEX_SUBAGENT_REASONING_EFFORT="${CODEX_SUBAGENT_REASONING_EFFORT-low}"
45
59
  EXTERNAL_TOOLS="${CODEX_EXTERNAL_TOOLS:-false}"
46
60
  CODEX_CONTEXT_WINDOW="${CODEX_CONTEXT_WINDOW:-5000000}"
47
61
  CODEX_MAX_CONTEXT_WINDOW="${CODEX_MAX_CONTEXT_WINDOW:-}"
48
62
  CODEX_AUTO_COMPACT_TOKEN_LIMIT="${CODEX_AUTO_COMPACT_TOKEN_LIMIT:-4500000}"
49
63
  CODEX_REASONING_EFFORT="${CODEX_REASONING_EFFORT:-max}"
50
-
51
- # Codex version that supports the legacy multi-agent v1 config (plain tool names).
52
- SUBAGENT_CODEX_VERSION="0.132.0"
64
+ # How long Codex waits on a silent stream before giving up. The marathon models
65
+ # at max reasoning effort routinely think for longer than five minutes without
66
+ # emitting a token, and a subagent's turn is one uninterrupted think with no
67
+ # visible progress, so the old 300s ceiling cut off work that was still running.
68
+ CODEX_STREAM_IDLE_TIMEOUT_MS="${CODEX_STREAM_IDLE_TIMEOUT_MS:-900000}"
69
+ # Codex gates its subagent tools on this per-model catalog field. Only "v2" is
70
+ # honored by current releases; "v1" registers nothing. Set to empty to opt out.
71
+ # Unset defaults to v2; an explicitly empty value opts out, so use `-` not `:-`.
72
+ CODEX_MULTI_AGENT_VERSION="${CODEX_MULTI_AGENT_VERSION-v2}"
53
73
 
54
74
  # Parse args (only when executed, not sourced)
55
75
  PASSTHRU=()
@@ -72,9 +92,17 @@ if [[ "${BASH_SOURCE[0]:-$0}" == "${0}" ]]; then
72
92
  CODEX_REASONING_EFFORT="${2:-}"
73
93
  shift 2
74
94
  ;;
75
- --subagents)
76
- SUBAGENTS=true
77
- shift
95
+ --stream-idle-timeout)
96
+ CODEX_STREAM_IDLE_TIMEOUT_MS="${2:-}"
97
+ shift 2
98
+ ;;
99
+ --max-subagents)
100
+ MAX_CONCURRENT_SUBAGENTS="${2:-}"
101
+ shift 2
102
+ ;;
103
+ --subagent-effort)
104
+ CODEX_SUBAGENT_REASONING_EFFORT="${2:-}"
105
+ shift 2
78
106
  ;;
79
107
  --external-tools)
80
108
  EXTERNAL_TOOLS=true
@@ -105,7 +133,8 @@ esac
105
133
  DEFAULT_SUBCONSCIOUS_MODELS="subconscious/glm-5.3-marathon
106
134
  subconscious/glm-5.2
107
135
  subconscious/tim-qwen3.6-27b
108
- subconscious/deepseek-v4-flash-marathon"
136
+ subconscious/deepseek-v4-flash-marathon
137
+ subconscious/deepseek-v4.1-flash-marathon"
109
138
  SUPPORTED_MODELS=()
110
139
 
111
140
  add_supported_model() {
@@ -132,10 +161,17 @@ while IFS= read -r model_id; do
132
161
  done <<< "${SUBCONSCIOUS_MODELS:-$DEFAULT_SUBCONSCIOUS_MODELS}"
133
162
 
134
163
  write_model_catalog() {
135
- local catalog_file="$1" model_id index=0
164
+ local catalog_file="$1" model_id vision_fields index=0 multi_agent_json="null"
165
+ if [[ -n "$CODEX_MULTI_AGENT_VERSION" ]]; then
166
+ multi_agent_json="\"${CODEX_MULTI_AGENT_VERSION}\""
167
+ fi
136
168
  {
137
169
  printf '{\n "models": [\n'
138
170
  for model_id in "${SUPPORTED_MODELS[@]}"; do
171
+ vision_fields=""
172
+ if subc_model_supports_vision "$model_id"; then
173
+ vision_fields=', "input_modalities": ["text", "image"]'
174
+ fi
139
175
  if [[ "$index" -gt 0 ]]; then
140
176
  printf ',\n'
141
177
  fi
@@ -148,7 +184,13 @@ write_model_catalog() {
148
184
  "max_context_window": ${CODEX_MAX_CONTEXT_WINDOW},
149
185
  "auto_compact_token_limit": ${CODEX_AUTO_COMPACT_TOKEN_LIMIT},
150
186
  "effective_context_window_percent": 95,
151
- "supported_reasoning_levels": [],
187
+ "supported_reasoning_levels": [
188
+ { "effort": "none", "description": "No additional reasoning" },
189
+ { "effort": "low", "description": "Fast responses with lighter reasoning" },
190
+ { "effort": "medium", "description": "Balances speed and reasoning depth" },
191
+ { "effort": "high", "description": "Greater reasoning depth for complex problems" },
192
+ { "effort": "max", "description": "Maximum reasoning depth for the hardest problems" }
193
+ ],
152
194
  "shell_type": "shell_command",
153
195
  "visibility": "list",
154
196
  "supported_in_api": true,
@@ -169,7 +211,9 @@ write_model_catalog() {
169
211
  "apply_patch_tool_type": "freeform",
170
212
  "truncation_policy": { "mode": "tokens", "limit": 10000 },
171
213
  "supports_parallel_tool_calls": true,
172
- "experimental_supported_tools": []
214
+ "multi_agent_version": ${multi_agent_json},
215
+ "use_responses_lite": false,
216
+ "experimental_supported_tools": []${vision_fields}
173
217
  }
174
218
  EOF
175
219
  index=$((index + 1))
@@ -191,6 +235,41 @@ if [[ "$EXTERNAL_TOOLS" != "true" ]]; then
191
235
  )
192
236
  fi
193
237
 
238
+ # Bound the subagent tree when the catalog turns it on. Codex still only spawns
239
+ # a subagent when the user asks for one; these are ceilings, not defaults.
240
+ #
241
+ # Codex documents no nesting-depth limit, so a subagent can spawn its own
242
+ # subagent and the tree can grow deeper than you asked for. Capping concurrency
243
+ # is the only lever available.
244
+ #
245
+ # Subagent reasoning effort is set separately from the parent's on purpose: the
246
+ # marathon models at max effort think for many minutes without emitting a
247
+ # token, and a subagent turn is one uninterrupted think, so inheriting max
248
+ # makes every delegated subtask look like a hang.
249
+ # Applied after argument parsing so --max-subagents can win. Four is a floor
250
+ # rather than a considered limit: Codex's own default is three, and exceeding
251
+ # either one fails the spawn outright rather than queueing it, so a run that
252
+ # wants more should say so instead of collecting retries.
253
+ MAX_CONCURRENT_SUBAGENTS="${MAX_CONCURRENT_SUBAGENTS:-4}"
254
+
255
+ SUBAGENT_ARGS=()
256
+ if [[ -z "$CODEX_MULTI_AGENT_VERSION" ]]; then
257
+ # Clearing the catalog field alone only downgrades Codex to multi-agent v1,
258
+ # whose tools this gateway has never been exercised against. Turn the
259
+ # feature off outright instead.
260
+ SUBAGENT_ARGS=(-c agents.enabled=false)
261
+ else
262
+ SUBAGENT_ARGS=(
263
+ -c agents.max_concurrent_threads_per_session="${MAX_CONCURRENT_SUBAGENTS}"
264
+ -c agents.interrupt_message=true
265
+ )
266
+ if [[ -n "$CODEX_SUBAGENT_REASONING_EFFORT" ]]; then
267
+ SUBAGENT_ARGS+=(
268
+ -c agents.default_subagent_reasoning_effort="${CODEX_SUBAGENT_REASONING_EFFORT}"
269
+ )
270
+ fi
271
+ fi
272
+
194
273
  if [[ -z "$GATEWAY_URL" || -z "$API_KEY" ]]; then
195
274
  echo "error: GATEWAY_URL and API_KEY must be set in ../.env" >&2
196
275
  exit 1
@@ -200,6 +279,10 @@ export SUBCONSCIOUS_API_KEY="$API_KEY"
200
279
  export SUBCONSCIOUS_GATEWAY_URL="${GATEWAY_URL%/}"
201
280
 
202
281
  # Merge compaction hooks without replacing ~/.codex/hooks.json or config.toml.
282
+ # `codex_ensure_hooks` returns early when they are already current, so a launch
283
+ # that changes nothing writes nothing: Codex decides what counts as a new hook
284
+ # from what is on disk, and rewriting an identical one reset the trust the user
285
+ # had already granted.
203
286
  HOOK_SRC="${SCRIPT_DIR}/hook.sh"
204
287
  # shellcheck source=hooks-lib.sh
205
288
  source "${SCRIPT_DIR}/hooks-lib.sh"
@@ -214,43 +297,22 @@ write_model_catalog "$CATALOG_FILE"
214
297
 
215
298
  # If sourced, just export env and return.
216
299
  if [[ "${BASH_SOURCE[0]:-$0}" != "${0}" ]]; then
217
- export GATEWAY_URL CATALOG_FILE MAX_CONCURRENT_SUBAGENTS SUBAGENTS
300
+ export GATEWAY_URL CATALOG_FILE MAX_CONCURRENT_SUBAGENTS CODEX_SUBAGENT_REASONING_EFFORT
218
301
  return 0 2>/dev/null || true
219
302
  fi
220
303
 
221
304
  # Ephemeral config via -c flags — nothing is written to ~/.codex/config.toml.
222
- if [[ "$SUBAGENTS" == "true" ]]; then
223
- # Legacy multi-agent v1 config — plain tool names the model can resolve.
224
- echo "Starting codex@${SUBAGENT_CODEX_VERSION} with subagents enabled (max ${MAX_CONCURRENT_SUBAGENTS} threads)..." >&2
225
- exec npx -y "@openai/codex@${SUBAGENT_CODEX_VERSION}" \
226
- -c model="${MODEL}" \
227
- -c model_provider=subconscious \
228
- -c model_catalog_json="${CATALOG_FILE}" \
229
- -c model_reasoning_effort="${CODEX_REASONING_EFFORT}" \
230
- -c web_search=disabled \
231
- ${EXTERNAL_TOOL_ARGS[@]+"${EXTERNAL_TOOL_ARGS[@]}"} \
232
- -c features.multi_agent=true \
233
- -c agents.max_threads="${MAX_CONCURRENT_SUBAGENTS}" \
234
- -c agents.max_depth=1 \
235
- -c agents.interrupt_message=true \
236
- -c model_providers.subconscious.name=Subconscious \
237
- -c model_providers.subconscious.base_url="${GATEWAY_URL}/v1" \
238
- -c model_providers.subconscious.wire_api=responses \
239
- -c model_providers.subconscious.env_key=SUBCONSCIOUS_API_KEY \
240
- -c model_providers.subconscious.stream_idle_timeout_ms=300000 \
241
- ${PASSTHRU[@]+"${PASSTHRU[@]}"}
242
- else
243
- exec codex \
244
- -c model="${MODEL}" \
245
- -c model_provider=subconscious \
246
- -c model_catalog_json="${CATALOG_FILE}" \
247
- -c model_reasoning_effort="${CODEX_REASONING_EFFORT}" \
248
- -c web_search=disabled \
249
- ${EXTERNAL_TOOL_ARGS[@]+"${EXTERNAL_TOOL_ARGS[@]}"} \
250
- -c model_providers.subconscious.name=Subconscious \
251
- -c model_providers.subconscious.base_url="${GATEWAY_URL}/v1" \
252
- -c model_providers.subconscious.wire_api=responses \
253
- -c model_providers.subconscious.env_key=SUBCONSCIOUS_API_KEY \
254
- -c model_providers.subconscious.stream_idle_timeout_ms=300000 \
255
- ${PASSTHRU[@]+"${PASSTHRU[@]}"}
256
- fi
305
+ exec codex \
306
+ -c model="${MODEL}" \
307
+ -c model_provider=subconscious \
308
+ -c model_catalog_json="${CATALOG_FILE}" \
309
+ -c model_reasoning_effort="${CODEX_REASONING_EFFORT}" \
310
+ -c web_search=disabled \
311
+ ${EXTERNAL_TOOL_ARGS[@]+"${EXTERNAL_TOOL_ARGS[@]}"} \
312
+ ${SUBAGENT_ARGS[@]+"${SUBAGENT_ARGS[@]}"} \
313
+ -c model_providers.subconscious.name=Subconscious \
314
+ -c model_providers.subconscious.base_url="${GATEWAY_URL}/v1" \
315
+ -c model_providers.subconscious.wire_api=responses \
316
+ -c model_providers.subconscious.env_key=SUBCONSCIOUS_API_KEY \
317
+ -c model_providers.subconscious.stream_idle_timeout_ms="${CODEX_STREAM_IDLE_TIMEOUT_MS}" \
318
+ ${PASSTHRU[@]+"${PASSTHRU[@]}"}
@@ -64,6 +64,7 @@
64
64
  set -euo pipefail
65
65
 
66
66
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
67
+ source "${SCRIPT_DIR}/../model-capabilities.generated.sh"
67
68
  HOOK_SRC="${SCRIPT_DIR}/hook.sh"
68
69
  HOOKS_TEMPLATE="${SCRIPT_DIR}/hooks.json"
69
70
 
@@ -177,7 +178,8 @@ done
177
178
  DEFAULT_SUBCONSCIOUS_MODELS="subconscious/glm-5.3-marathon
178
179
  subconscious/glm-5.2
179
180
  subconscious/tim-qwen3.6-27b
180
- subconscious/deepseek-v4-flash-marathon"
181
+ subconscious/deepseek-v4-flash-marathon
182
+ subconscious/deepseek-v4.1-flash-marathon"
181
183
  SUPPORTED_MODELS=()
182
184
 
183
185
  add_supported_model() {
@@ -294,13 +296,16 @@ write_config() {
294
296
  local existing
295
297
  existing="$(strip_subconscious "$models_json")"
296
298
 
297
- local provider_models='[]' model_id
299
+ local provider_models='[]' model_id vision
298
300
  for model_id in "${SUPPORTED_MODELS[@]}"; do
301
+ vision=false
302
+ if subc_model_supports_vision "$model_id"; then vision=true; fi
299
303
  provider_models=$(jq -cn \
300
304
  --argjson models "$provider_models" \
301
305
  --arg modelId "$model_id" \
302
306
  --arg modelName "Subconscious ${model_id}" \
303
307
  --arg url "$messages_url" \
308
+ --argjson vision "$vision" \
304
309
  --argjson maxIn "$MAX_INPUT_TOKENS" \
305
310
  --argjson maxOut "$MAX_OUTPUT_TOKENS" \
306
311
  '$models + [{
@@ -308,7 +313,7 @@ write_config() {
308
313
  name: $modelName,
309
314
  url: $url,
310
315
  toolCalling: true,
311
- vision: false,
316
+ vision: $vision,
312
317
  maxInputTokens: $maxIn,
313
318
  maxOutputTokens: $maxOut,
314
319
  thinking: true,
@@ -59,7 +59,8 @@ COMMAND="install"
59
59
  DEFAULT_SUBCONSCIOUS_MODELS="subconscious/glm-5.3-marathon
60
60
  subconscious/glm-5.2
61
61
  subconscious/tim-qwen3.6-27b
62
- subconscious/deepseek-v4-flash-marathon"
62
+ subconscious/deepseek-v4-flash-marathon
63
+ subconscious/deepseek-v4.1-flash-marathon"
63
64
 
64
65
  SUPPORTED_MODELS=()
65
66
  add_supported_model() {
@@ -4,6 +4,9 @@
4
4
 
5
5
  set -euo pipefail
6
6
 
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ source "${SCRIPT_DIR}/../model-capabilities.generated.sh"
9
+
7
10
  GATEWAY_URL="${GATEWAY_URL:-}"
8
11
  API_KEY="${DEEPSEEK_HARNESS_API_KEY:-${API_KEY:-}}"
9
12
  MODEL="${MODEL:-subconscious/glm-5.3-marathon}"
@@ -13,7 +16,8 @@ MAX_TOKENS="${DEEPSEEK_HARNESS_MAX_TOKENS:-65536}"
13
16
  DEFAULT_SUBCONSCIOUS_MODELS="subconscious/glm-5.3-marathon
14
17
  subconscious/glm-5.2
15
18
  subconscious/tim-qwen3.6-27b
16
- subconscious/deepseek-v4-flash-marathon"
19
+ subconscious/deepseek-v4-flash-marathon
20
+ subconscious/deepseek-v4.1-flash-marathon"
17
21
 
18
22
  if [[ -z "$GATEWAY_URL" || -z "$API_KEY" ]]; then
19
23
  echo "error: GATEWAY_URL and API_KEY are required to launch DeepSeek Harness" >&2
@@ -79,6 +83,9 @@ trap cleanup EXIT HUP INT TERM
79
83
  for model_id in "${SUPPORTED_MODELS[@]}"; do
80
84
  printf " - id: '%s'\n" "$model_id"
81
85
  printf " name: '%s'\n" "$model_id"
86
+ if subc_model_supports_vision "$model_id"; then
87
+ printf '%s\n' ' input: [text, image]'
88
+ fi
82
89
  printf " contextWindow: %s\n" "$CONTEXT_WINDOW"
83
90
  printf " maxTokens: %s\n" "$MAX_TOKENS"
84
91
  done
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env bash
2
+ # Generated from agents/registry.json. Do not edit by hand.
3
+ # Exact IDs only: similarly named models do not inherit capabilities.
4
+ subc_model_supports_vision() {
5
+ case "${1:-}" in
6
+ 'subconscious/deepseek-v4.1-flash-marathon') return 0 ;;
7
+ *) return 1 ;;
8
+ esac
9
+ }
@@ -54,6 +54,7 @@
54
54
  set -euo pipefail
55
55
 
56
56
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
57
+ source "${SCRIPT_DIR}/../model-capabilities.generated.sh"
57
58
 
58
59
  # Load shared env from SUBC_ENV_FILE, or a sibling .env / env.example.
59
60
  SHARED_ENV="${SUBC_ENV_FILE:-${SCRIPT_DIR}/../.env}"
@@ -122,7 +123,8 @@ done
122
123
  DEFAULT_SUBCONSCIOUS_MODELS="subconscious/glm-5.3-marathon
123
124
  subconscious/glm-5.2
124
125
  subconscious/tim-qwen3.6-27b
125
- subconscious/deepseek-v4-flash-marathon"
126
+ subconscious/deepseek-v4-flash-marathon
127
+ subconscious/deepseek-v4.1-flash-marathon"
126
128
  SUPPORTED_MODELS=()
127
129
 
128
130
  add_supported_model() {
@@ -147,7 +149,11 @@ done <<< "${SUBCONSCIOUS_MODELS:-$DEFAULT_SUBCONSCIOUS_MODELS}"
147
149
 
148
150
  MODELS_JSON=""
149
151
  for model_id in "${SUPPORTED_MODELS[@]}"; do
150
- model_json="\"${model_id}\":{\"name\":\"${model_id}\",\"tools\":true,\"limit\":{\"context\":${CONTEXT_LIMIT},\"output\":${OUTPUT_LIMIT}}}"
152
+ vision_fields=""
153
+ if subc_model_supports_vision "$model_id"; then
154
+ vision_fields=',"attachment":true,"modalities":{"input":["text","image"],"output":["text"]}'
155
+ fi
156
+ model_json="\"${model_id}\":{\"name\":\"${model_id}\",\"tools\":true,\"limit\":{\"context\":${CONTEXT_LIMIT},\"output\":${OUTPUT_LIMIT}}${vision_fields}}"
151
157
  if [[ -n "$MODELS_JSON" ]]; then
152
158
  MODELS_JSON="${MODELS_JSON},${model_json}"
153
159
  else
@@ -18,6 +18,7 @@
18
18
  set -euo pipefail
19
19
 
20
20
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21
+ source "${SCRIPT_DIR}/../model-capabilities.generated.sh"
21
22
 
22
23
  # Load shared env from SUBC_ENV_FILE, or a sibling .env / env.example.
23
24
  SHARED_ENV="${SUBC_ENV_FILE:-${SCRIPT_DIR}/../.env}"
@@ -33,7 +34,8 @@ OUTPUT_LIMIT="${OPENCODE_OUTPUT_LIMIT:-65536}"
33
34
  DEFAULT_SUBCONSCIOUS_MODELS="subconscious/glm-5.3-marathon
34
35
  subconscious/glm-5.2
35
36
  subconscious/tim-qwen3.6-27b
36
- subconscious/deepseek-v4-flash-marathon"
37
+ subconscious/deepseek-v4-flash-marathon
38
+ subconscious/deepseek-v4.1-flash-marathon"
37
39
  SUPPORTED_MODELS=()
38
40
 
39
41
  add_supported_model() {
@@ -58,7 +60,11 @@ done <<< "${SUBCONSCIOUS_MODELS:-$DEFAULT_SUBCONSCIOUS_MODELS}"
58
60
 
59
61
  MODELS_JSON=""
60
62
  for model_id in "${SUPPORTED_MODELS[@]}"; do
61
- model_json="\"${model_id}\":{\"name\":\"${model_id}\",\"tools\":true,\"limit\":{\"context\":${CONTEXT_LIMIT},\"output\":${OUTPUT_LIMIT}}}"
63
+ vision_fields=""
64
+ if subc_model_supports_vision "$model_id"; then
65
+ vision_fields=',"attachment":true,"modalities":{"input":["text","image"],"output":["text"]}'
66
+ fi
67
+ model_json="\"${model_id}\":{\"name\":\"${model_id}\",\"tools\":true,\"limit\":{\"context\":${CONTEXT_LIMIT},\"output\":${OUTPUT_LIMIT}}${vision_fields}}"
62
68
  if [[ -n "$MODELS_JSON" ]]; then
63
69
  MODELS_JSON="${MODELS_JSON},${model_json}"
64
70
  else
@@ -47,6 +47,7 @@
47
47
  set -euo pipefail
48
48
 
49
49
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
50
+ source "${SCRIPT_DIR}/../model-capabilities.generated.sh"
50
51
  EXTENSION_SRC="${SCRIPT_DIR}/subconscious-compaction.ts"
51
52
 
52
53
  # Load shared env from SUBC_ENV_FILE, or a sibling .env / env.example.
@@ -117,7 +118,8 @@ done
117
118
  DEFAULT_SUBCONSCIOUS_MODELS="subconscious/glm-5.3-marathon
118
119
  subconscious/glm-5.2
119
120
  subconscious/tim-qwen3.6-27b
120
- subconscious/deepseek-v4-flash-marathon"
121
+ subconscious/deepseek-v4-flash-marathon
122
+ subconscious/deepseek-v4.1-flash-marathon"
121
123
  SUPPORTED_MODELS=()
122
124
 
123
125
  add_supported_model() {
@@ -142,7 +144,11 @@ done <<< "${SUBCONSCIOUS_MODELS:-$DEFAULT_SUBCONSCIOUS_MODELS}"
142
144
 
143
145
  MODEL_ENTRIES_JSON=""
144
146
  for model_id in "${SUPPORTED_MODELS[@]}"; do
145
- model_json="{\"id\":\"${model_id}\",\"contextWindow\":${CONTEXT_WINDOW},\"maxTokens\":${MAX_TOKENS},\"compat\":{\"sendSessionAffinityHeaders\":true,\"sessionAffinityFormat\":\"openai-nosession\"}}"
147
+ vision_fields=""
148
+ if subc_model_supports_vision "$model_id"; then
149
+ vision_fields=',"input":["text","image"]'
150
+ fi
151
+ model_json="{\"id\":\"${model_id}\",\"contextWindow\":${CONTEXT_WINDOW},\"maxTokens\":${MAX_TOKENS},\"compat\":{\"sendSessionAffinityHeaders\":true,\"sessionAffinityFormat\":\"openai-nosession\"}${vision_fields}}"
146
152
  if [[ -n "$MODEL_ENTRIES_JSON" ]]; then
147
153
  MODEL_ENTRIES_JSON="${MODEL_ENTRIES_JSON},${model_json}"
148
154
  else
@@ -3,6 +3,7 @@ import fs from 'node:fs/promises';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { claudeNativeLaunch } from './claude.js';
6
+ import { modelSupportsVision } from '../model-capabilities.js';
6
7
  import { defaults, value, origin, modelIds, positiveInteger, parseOptions, writeJson } from './common.js';
7
8
 
8
9
  function connection(env, keyName) {
@@ -25,7 +26,10 @@ export async function windowsLaunch(id, argv, env, { tempRoot = os.tmpdir() } =
25
26
  if (id === 'opencode') {
26
27
  const context = positiveInteger(value(env, 'OPENCODE_CONTEXT_LIMIT', '5000000'), 'OPENCODE_CONTEXT_LIMIT');
27
28
  const output = positiveInteger(value(env, 'OPENCODE_OUTPUT_LIMIT', '65536'), 'OPENCODE_OUTPUT_LIMIT');
28
- const models = Object.fromEntries(modelIds(env).map(id => [id, { name: id, tools: true, limit: { context, output } }]));
29
+ const models = Object.fromEntries(modelIds(env).map(id => [id, {
30
+ name: id, tools: true, limit: { context, output },
31
+ ...(modelSupportsVision(id) ? { attachment: true, modalities: { input: ['text', 'image'], output: ['text'] } } : {}),
32
+ }]));
29
33
  childEnv.OPENCODE_CONFIG_CONTENT = JSON.stringify({
30
34
  $schema: 'https://opencode.ai/config.json', disabled_providers: ['subconscious'],
31
35
  provider: { 'subconscious-cli': { npm: '@ai-sdk/openai-compatible', name: 'Subconscious Gateway', options: {
@@ -51,6 +55,7 @@ export async function windowsLaunch(id, argv, env, { tempRoot = os.tmpdir() } =
51
55
  supports_reasoning_summaries: false, support_verbosity: false, default_verbosity: null,
52
56
  apply_patch_tool_type: 'freeform', truncation_policy: { mode: 'tokens', limit: 10000 },
53
57
  supports_parallel_tool_calls: true, experimental_supported_tools: [],
58
+ ...(modelSupportsVision(id) ? { input_modalities: ['text', 'image'] } : {}),
54
59
  })) };
55
60
  // Validate all flags before creating a temporary directory.
56
61
  const threads = positiveInteger(value(env, 'MAX_CONCURRENT_SUBAGENTS', '4'), 'MAX_CONCURRENT_SUBAGENTS');
@@ -81,7 +86,7 @@ export async function windowsLaunch(id, argv, env, { tempRoot = os.tmpdir() } =
81
86
  const cleanup = () => fs.rm(dir, { recursive: true, force: true });
82
87
  try {
83
88
  const file = path.join(dir, 'subconscious.cordis.yml');
84
- const text = `- id: llm-pi-ai\n config:\n providers:\n subconscious:\n apiKeyEnv: SUBCONSCIOUS_API_KEY\n displayName: Subconscious Gateway\n api: openai-completions\n baseURL: !!js process.env.SUBCONSCIOUS_DSH_BASE_URL\n headers:\n x-subconscious-client: deepseek-harness\n compat:\n supportsDeveloperRole: false\n maxTokensField: max_tokens\n defaultContextWindow: ${context}\n defaultMaxTokens: ${maxTokens}\n models:\n${models.map(id => ` - id: '${id}'\n name: '${id}'\n contextWindow: ${context}\n maxTokens: ${maxTokens}`).join('\n')}\n- id: agent-default-model\n config:\n provider: subconscious\n model: '${model}'\n`;
89
+ const text = `- id: llm-pi-ai\n config:\n providers:\n subconscious:\n apiKeyEnv: SUBCONSCIOUS_API_KEY\n displayName: Subconscious Gateway\n api: openai-completions\n baseURL: !!js process.env.SUBCONSCIOUS_DSH_BASE_URL\n headers:\n x-subconscious-client: deepseek-harness\n compat:\n supportsDeveloperRole: false\n maxTokensField: max_tokens\n defaultContextWindow: ${context}\n defaultMaxTokens: ${maxTokens}\n models:\n${models.map(id => ` - id: '${id}'\n name: '${id}'\n${modelSupportsVision(id) ? ' input: [text, image]\n' : ''} contextWindow: ${context}\n maxTokens: ${maxTokens}`).join('\n')}\n- id: agent-default-model\n config:\n provider: subconscious\n model: '${model}'\n`;
85
90
  await fs.writeFile(file, text, { mode: 0o600, flag: 'wx' });
86
91
  const mode = ['web', 'headless'].includes(argv[0]) ? argv[0] : 'web';
87
92
  const rest = ['web', 'headless'].includes(argv[0]) ? argv.slice(1) : argv;
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
+ import { modelSupportsVision } from '../model-capabilities.js';
4
5
  import { defaults, origin, value, modelIds, positiveInteger, parseOptions, object, readJson, writeJson } from './common.js';
5
6
 
6
7
  const owned = command => {
@@ -92,7 +93,7 @@ export async function windowsSetup(id, action, argv, environment, { home = os.ho
92
93
  const context = positiveInteger(options['--max-input-tokens'] || value(env, 'COPILOT_MAX_INPUT_TOKENS', '5000000'), 'max-input-tokens');
93
94
  const maxTokens = positiveInteger(options['--max-output-tokens'] || value(env, 'COPILOT_MAX_OUTPUT_TOKENS', '65536'), 'max-output-tokens');
94
95
  providers.push({ name: providerName, vendor: 'customendpoint', apiKey: '${input:chat.lm.secret.subconscious-gateway}', apiType: 'messages', models: modelIds(env).map(id => ({
95
- id, name: `Subconscious ${id}`, url: `${gatewayUrl}/v1/messages`, toolCalling: true, vision: false,
96
+ id, name: `Subconscious ${id}`, url: `${gatewayUrl}/v1/messages`, toolCalling: true, vision: modelSupportsVision(id),
96
97
  maxInputTokens: context, maxOutputTokens: maxTokens, thinking: true, streaming: true,
97
98
  requestHeaders: { 'x-subconscious-client': 'copilot' },
98
99
  })) });
@@ -131,7 +132,10 @@ export async function windowsSetup(id, action, argv, environment, { home = os.ho
131
132
  if (installing) {
132
133
  const context = positiveInteger(options['--context-window'] || value(env, 'PI_CONTEXT_WINDOW', '5000000'), 'context-window');
133
134
  const maxTokens = positiveInteger(options['--max-tokens'] || value(env, 'PI_MAX_TOKENS', '65536'), 'max-tokens');
134
- providers.subconscious = { baseUrl: `${gatewayUrl}/v1`, api: 'openai-completions', apiKey, headers: { 'x-subconscious-client': 'pi' }, models: modelIds(env).map(id => ({ id, contextWindow: context, maxTokens, compat: { sendSessionAffinityHeaders: true, sessionAffinityFormat: 'openai-nosession' } })) };
135
+ providers.subconscious = { baseUrl: `${gatewayUrl}/v1`, api: 'openai-completions', apiKey, headers: { 'x-subconscious-client': 'pi' }, models: modelIds(env).map(id => ({
136
+ id, contextWindow: context, maxTokens, compat: { sendSessionAffinityHeaders: true, sessionAffinityFormat: 'openai-nosession' },
137
+ ...(modelSupportsVision(id) ? { input: ['text', 'image'] } : {}),
138
+ })) };
135
139
  await fs.mkdir(path.dirname(extension), { recursive: true });
136
140
  await fs.copyFile(new URL('./pi-compaction.ts', import.meta.url), extension);
137
141
  await writeJson(config, { gatewayUrl, apiKey });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "subconscious-cli",
3
- "version": "4.1.2",
3
+ "version": "4.1.4",
4
4
  "description": "CLI for Subconscious — run Subconscious Code, Claude Code, Codex, OpenCode, DeepSeek Harness, Cursor, Copilot, and Pi",
5
5
  "bin": {
6
6
  "subc": "bin/cli.js"
@@ -9,7 +9,7 @@
9
9
  "build:tui": "node scripts/build-tui.js",
10
10
  "build:tui:host": "node scripts/build-tui.js --host",
11
11
  "test": "node --test",
12
- "test:windows": "node --test test/windows.test.js test/models.test.js test/auth.test.js test/tui.test.js test/update-check.test.js test/sessions.test.js test/branding.test.js test/claude-version.test.js test/usage.test.js test/release.test.js",
12
+ "test:windows": "node --test test/windows.test.js test/models.test.js test/model-capabilities.test.js test/auth.test.js test/tui.test.js test/update-check.test.js test/sessions.test.js test/branding.test.js test/claude-version.test.js test/usage.test.js test/release.test.js",
13
13
  "test:tui": "go -C tui test ./...",
14
14
  "prepack": "npm run build:tui",
15
15
  "prepublishOnly": "node scripts/require-publish-script.js"