Skip to content

Commit b7620e9

Browse files
authored
Merge pull request #16 from BaseInfinity/v0.12.0-opencode-zen
v0.12.0: managed tier with OpenCode Zen
2 parents 626e349 + 98a6c14 commit b7620e9

8 files changed

Lines changed: 243 additions & 2 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Tiers (privacy-first ordering):
8282
| `enterprise` | Stays in your tenant | Azure OpenAI, AWS Bedrock |
8383
| `hosted_oss` | Open weights, third-party host | Together, Groq, OpenRouter, Cerebras, DeepSeek direct, NVIDIA NIM (`nvidia_nim`) |
8484
| `proprietary` | Vendor-bound | Anthropic, OpenAI, Google AI Studio (`google_aistudio` — Gemini), Z.AI (`zai` — GLM Coding Plan, post-Anthropic-OAuth-ban migration target) |
85+
| `managed` | OpenCode-routed PAYG | OpenCode Zen (`opencode` — 40+ models incl. free tier; official new-user entry point) |
8586

8687
See [`docs/cost-ladder.md`](docs/cost-ladder.md) for $0/$20/$200 monthly
8788
budget paths and a per-job picker (routine fix vs long-context refactor

CHANGELOG.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,101 @@
22

33
All notable changes to opencode-sdlc-wizard.
44

5+
## [0.12.0] - 2026-05-18
6+
7+
### Added — `managed` tier with OpenCode Zen as 5th privacy tier
8+
9+
The first new tier since v0.2.0. **OpenCode Zen** is OpenCode's own
10+
vendor-routed PAYG service — 40+ models including a free tier (Big
11+
Pickle, DeepSeek V4 Flash Free, MiniMax M2.5 Free, Nemotron 3 Super
12+
Free), $5 auto-reload trigger. Per May-2026 research, Zen is the
13+
"official recommended new-user entry point" — the wizard now reflects
14+
this in both the detector cascade and the tier table.
15+
16+
```bash
17+
export OPENCODE_ZEN_API_KEY="..."
18+
npx opencode-sdlc-wizard pick # auto-detected
19+
npx opencode-sdlc-wizard pick --tier managed --provider opencode
20+
npx opencode-sdlc-wizard pick --tier managed --provider zen # alias
21+
```
22+
23+
Yields:
24+
25+
```json
26+
{
27+
"model": "opencode/gpt-5.5",
28+
"provider": {
29+
"opencode": {
30+
"npm": "@ai-sdk/openai-compatible",
31+
"options": {
32+
"apiKey": "{env:OPENCODE_ZEN_API_KEY}",
33+
"baseURL": "https://opencode.ai/zen/v1"
34+
},
35+
"models": { "gpt-5.5": {} }
36+
}
37+
}
38+
}
39+
```
40+
41+
### Verified live (Zen docs, fetched 2026-05-18)
42+
43+
- **Canonical provider id**: `opencode` (per Zen's docs — model pin format `opencode/<model>`)
44+
- **baseURL**: `https://opencode.ai/zen/v1` (OpenAI-compatible)
45+
- **Auth**: `{env:OPENCODE_ZEN_API_KEY}` — namespaced to avoid collision with anything else "opencode"
46+
- **Default model**: `gpt-5.5` (Zen's own documented example string)
47+
- **Pricing**: PAYG per 1M tokens; free-tier models available; $5 reload trigger / default $20 reload
48+
49+
### New 5th tier — `managed`
50+
51+
| Tier | Privacy semantics |
52+
|---|---|
53+
| `private_local` | Your machine, zero egress |
54+
| `enterprise` | Your tenant (Azure, Bedrock) |
55+
| `hosted_oss` | Open weights, third-party host (you pick the upstream) |
56+
| **`managed`** | **OpenCode-routed PAYG; vendor manages routing** |
57+
| `proprietary` | Vendor-bound (you bring the API key, vendor sees prompts) |
58+
59+
Position is intentional: prompts go through OpenCode's infra (less
60+
private than DIY hosted, more managed than your own vendor key). Sits
61+
between `hosted_oss` and `proprietary` in the privacy-first cascade.
62+
63+
### Changed
64+
65+
- `scripts/detect-backends.sh`:
66+
- New `M_OPENCODE_ZEN_SET="$(env_set OPENCODE_ZEN_API_KEY)"` probe
67+
- New `managed.opencode.{key_set, env}` entry in JSON output
68+
- Privacy-first cascade: `managed` placed between `hosted_oss` and `proprietary`
69+
- Free-tier-first cascade: `managed` placed between `proprietary/zai` and `proprietary/anthropic` (Zen has free models so beats paid Anthropic on cost)
70+
- `scripts/configure-backend.sh`:
71+
- `PROVIDER_ALIASES` (4 new entries): `opencode → opencode`, `opencode_zen → opencode`, `opencode-zen → opencode`, `zen → opencode`
72+
- `fragmentFor` new `managed/opencode` case with the verified config
73+
- `scripts/pick-backend.sh`:
74+
- `default_model_for()` new `managed/opencode|opencode_zen|opencode-zen|zen``gpt-5.5` entry
75+
- `AGENTS.md`: tier table gains a `managed` row
76+
77+
### Tests
78+
79+
- `tests/test-backend-picker.sh` adds T66–T69:
80+
- T66: configure-backend writes correct Zen provider block (id, baseURL, apiKey, npm adapter, models entry)
81+
- T67: alias resolution (`zen → opencode`)
82+
- T68: detector picks up `OPENCODE_ZEN_API_KEY` and recommends `managed/opencode` (fake HOME + stripped PATH)
83+
- T69: `managed/opencode` composes with v0.10.x/v0.11.x agent flags (reviewer + sandbox + temp), same-provider coder+reviewer collapses to single provider block
84+
- `tests/test-pick.sh` T12 default-model drift gate gains `managed/opencode:gpt-5.5` entry
85+
- **400 tests across 12 suites** (was 395 / 12 in v0.11.2)
86+
87+
### Why minor bump (v0.11.x → v0.12.0)
88+
89+
New top-level tier in the JSON output. Detector schema gained a new
90+
top-level key (`managed`). User-visible enough to warrant the minor.
91+
Purely additive — every v0.11.x flag and provider continues to work
92+
unchanged.
93+
94+
### Compat
95+
96+
- 16 of 16 v0.11.2 default-model entries unchanged.
97+
- Every existing tier + provider continues to work identically.
98+
- Every v0.10.x/v0.11.x flag works on the new `managed/opencode` tier+provider exactly as it does on the other tiers (verified by T69's full hybrid test).
99+
5100
## [0.11.2] - 2026-05-18
6101

7102
### Added — Security agent (full set: model + temperature + tools-denial sandbox)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opencode-sdlc-wizard",
3-
"version": "0.11.2",
3+
"version": "0.12.0",
44
"description": "SDLC enforcement for OpenCode CLI — privacy-first, any-backend portability with a four-tier backend picker plus an OSS-tier cross-model-review skill so the full SDLC loop can run with zero Anthropic+OpenAI lock-in. Ships JSON Schemas for review artifacts so any consumer (cross-model-review, ditto, CI) can validate. Install with `npx opencode-sdlc-wizard init`. Sibling of agentic-sdlc-wizard and codex-sdlc-wizard.",
55
"bin": {
66
"opencode-sdlc-wizard": "cli/bin/opencode-sdlc-wizard.js"

scripts/configure-backend.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# enterprise azure_openai / aws_bedrock
88
# hosted_oss together / groq / openrouter / cerebras / deepseek / nvidia_nim
99
# proprietary anthropic / openai / google_aistudio / zai
10+
# managed opencode (OpenCode Zen — vendor-routed PAYG over 40+ models)
1011
#
1112
# Usage:
1213
# configure-backend.sh --tier <tier> --provider <provider> --model <model>
@@ -263,6 +264,13 @@ const PROVIDER_ALIASES = {
263264
"z.ai": "zai",
264265
z_ai: "zai",
265266
glm: "zai",
267+
// v0.12.0: OpenCode Zen — managed tier, vendor-routed PAYG over 40+
268+
// models. Canonical provider id in opencode.json is "opencode" per
269+
// Zen's docs (model pin format: "opencode/<model>").
270+
opencode: "opencode",
271+
opencode_zen: "opencode",
272+
"opencode-zen": "opencode",
273+
zen: "opencode",
266274
};
267275
const provider = PROVIDER_ALIASES[providerArg] || providerArg;
268276
@@ -489,6 +497,26 @@ function fragmentFor(tier, provider, model) {
489497
},
490498
},
491499
};
500+
case "managed/opencode":
501+
// OpenCode Zen — vendor-managed routing over 40+ models including
502+
// a free tier (Big Pickle, DeepSeek V4 Flash Free, MiniMax M2.5
503+
// Free, Nemotron 3 Super Free). PAYG, $5 auto-reload trigger.
504+
// Provider id is "opencode" per Zen's own docs (model pin format
505+
// "opencode/<model>"). OpenAI-compatible endpoint at
506+
// https://opencode.ai/zen/v1.
507+
return {
508+
model: `opencode/${model}`,
509+
provider: {
510+
opencode: {
511+
npm: "@ai-sdk/openai-compatible",
512+
options: {
513+
apiKey: "{env:OPENCODE_ZEN_API_KEY}",
514+
baseURL: "https://opencode.ai/zen/v1",
515+
},
516+
models: { [model]: {} },
517+
},
518+
},
519+
};
492520
default:
493521
throw new Error(`unsupported tier/provider: ${t}`);
494522
}

scripts/detect-backends.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# "enterprise": { azure_openai, aws_bedrock },
88
# "hosted_oss": { together, groq, openrouter, cerebras, deepseek, nvidia_nim },
99
# "proprietary": { anthropic, openai, google_aistudio, zai },
10+
# "managed": { opencode (OpenCode Zen) },
1011
# "recommendation": "<tier>/<provider>"
1112
# }
1213
#
@@ -118,6 +119,12 @@ PR_ANTHROPIC_SET="$(env_set ANTHROPIC_API_KEY)"
118119
PR_OPENAI_SET="$(env_set OPENAI_API_KEY)"
119120
# v0.11.0: Z.AI GLM Coding Plan. Closed weights → proprietary tier.
120121
PR_ZAI_SET="$(env_set ZAI_API_KEY)"
122+
# v0.12.0: OpenCode Zen — managed tier. PAYG, OpenCode's hosted routing
123+
# service over 40+ models with a free tier. New 5th privacy tier
124+
# between hosted_oss and proprietary: it's vendor-managed (you don't
125+
# pick the upstream provider, Zen routes for you) but your prompts go
126+
# through OpenCode's infra, not Anthropic/OpenAI direct.
127+
M_OPENCODE_ZEN_SET="$(env_set OPENCODE_ZEN_API_KEY)"
121128

122129
# Recommendation cascade — privacy-first by default. When DETECT_FREE_TIER_FIRST=1
123130
# (set by configure-backend.sh's --free-tier-first), bias toward providers with
@@ -137,6 +144,10 @@ recommend_privacy_first() {
137144
if [ "$H_NVIDIA_SET" = "true" ]; then echo "hosted_oss/nvidia_nim"; return; fi
138145
if [ "$H_DEEPSEEK_SET" = "true" ]; then echo "hosted_oss/deepseek"; return; fi
139146
if [ "$H_OPENROUTER_SET" = "true" ]; then echo "hosted_oss/openrouter"; return; fi
147+
# managed tier (OpenCode Zen) sits between hosted_oss and proprietary in
148+
# the privacy-first cascade: prompts go through OpenCode's hosted infra
149+
# (less private than DIY hosted, more managed than your own vendor key).
150+
if [ "$M_OPENCODE_ZEN_SET" = "true" ]; then echo "managed/opencode"; return; fi
140151
if [ "$PR_ANTHROPIC_SET" = "true" ]; then echo "proprietary/anthropic"; return; fi
141152
if [ "$PR_OPENAI_SET" = "true" ]; then echo "proprietary/openai"; return; fi
142153
if [ "$H_GOOGLE_AISTUDIO_SET" = "true" ]; then echo "proprietary/google_aistudio"; return; fi
@@ -167,6 +178,10 @@ recommend_free_tier_first() {
167178
# community signal (May-2026 post-OAuth-ban migration target) ranks it
168179
# above the other proprietary tiers when cost matters more than ceiling.
169180
if [ "$PR_ZAI_SET" = "true" ]; then echo "proprietary/zai"; return; fi
181+
# OpenCode Zen also goes here — has a free tier (Big Pickle, DeepSeek
182+
# V4 Flash Free, MiniMax M2.5 Free, Nemotron 3 Super Free) so it
183+
# beats paid-per-token Anthropic/OpenAI on cost.
184+
if [ "$M_OPENCODE_ZEN_SET" = "true" ]; then echo "managed/opencode"; return; fi
170185
if [ "$PR_ANTHROPIC_SET" = "true" ]; then echo "proprietary/anthropic"; return; fi
171186
if [ "$PR_OPENAI_SET" = "true" ]; then echo "proprietary/openai"; return; fi
172187
echo "none"
@@ -207,6 +222,9 @@ cat <<EOF
207222
"google_aistudio": { "key_set": $H_GOOGLE_AISTUDIO_SET, "env": "GOOGLE_API_KEY" },
208223
"zai": { "key_set": $PR_ZAI_SET, "env": "ZAI_API_KEY" }
209224
},
225+
"managed": {
226+
"opencode": { "key_set": $M_OPENCODE_ZEN_SET, "env": "OPENCODE_ZEN_API_KEY" }
227+
},
210228
"recommendation": "$RECOMMENDATION"
211229
}
212230
EOF

scripts/pick-backend.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ default_model_for() {
216216
echo "gemini-3.1-pro" ;;
217217
proprietary/zai|proprietary/z.ai|proprietary/z_ai|proprietary/glm)
218218
echo "glm-4.6" ;;
219+
managed/opencode|managed/opencode_zen|managed/opencode-zen|managed/zen)
220+
echo "gpt-5.5" ;;
219221
*) return 1 ;;
220222
esac
221223
}

tests/test-backend-picker.sh

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,6 +1434,102 @@ console.log('ok');
14341434
fi
14351435
fi
14361436

1437+
# --- v0.12.0: OpenCode Zen as new 5th "managed" tier. Vendor-routed PAYG
1438+
# over 40+ models including a free tier. Provider id `opencode` (per Zen's
1439+
# own docs — model pin format `opencode/<model>`). Aliases: opencode_zen,
1440+
# opencode-zen, zen. baseURL https://opencode.ai/zen/v1, env OPENCODE_ZEN_API_KEY.
1441+
1442+
# --- T66: configure-backend writes correct OpenCode Zen provider block
1443+
if [ -x "$CONFIG" ]; then
1444+
T="$TMP_ROOT/t66"; mkdir -p "$T"
1445+
(cd "$T" && "$CONFIG" --tier managed --provider opencode --model "gpt-5.5" >/dev/null 2>&1) || true
1446+
if [ -f "$T/opencode.json" ]; then
1447+
ok="$(node -e "
1448+
const j=require('$T/opencode.json');
1449+
if(j.model!=='opencode/gpt-5.5'){console.log('model-wrong:'+j.model);process.exit(1)}
1450+
if(!j.provider||!j.provider.opencode){console.log('missing-opencode-block');process.exit(1)}
1451+
const opts=j.provider.opencode.options||{};
1452+
if(opts.apiKey!=='{env:OPENCODE_ZEN_API_KEY}'){console.log('wrong-apikey:'+opts.apiKey);process.exit(1)}
1453+
if(opts.baseURL!=='https://opencode.ai/zen/v1'){console.log('wrong-baseurl:'+opts.baseURL);process.exit(1)}
1454+
if(j.provider.opencode.npm!=='@ai-sdk/openai-compatible'){console.log('wrong-npm');process.exit(1)}
1455+
if(!j.provider.opencode.models||!j.provider.opencode.models['gpt-5.5']){console.log('missing-model-entry');process.exit(1)}
1456+
console.log('ok');
1457+
" 2>/dev/null || echo 'failed')"
1458+
if [ "$ok" = "ok" ]; then
1459+
pass "configure-backend writes OpenCode Zen provider block (managed/opencode, gpt-5.5)"
1460+
else
1461+
fail "T66 — $ok"
1462+
fi
1463+
fi
1464+
fi
1465+
1466+
# --- T67: Zen aliases (zen, opencode_zen, opencode-zen) resolve to canonical 'opencode'
1467+
if [ -x "$CONFIG" ]; then
1468+
T="$TMP_ROOT/t67"; mkdir -p "$T"
1469+
(cd "$T" && "$CONFIG" --tier managed --provider zen --model "gpt-5.5" >/dev/null 2>&1) || true
1470+
if [ -f "$T/opencode.json" ]; then
1471+
ok="$(node -e "
1472+
const j=require('$T/opencode.json');
1473+
if(j.model!=='opencode/gpt-5.5'){console.log('alias-pin-wrong:'+j.model);process.exit(1)}
1474+
if(!j.provider.opencode){console.log('alias-block-missing');process.exit(1)}
1475+
if(j.provider.zen){console.log('non-canonical-key-present');process.exit(1)}
1476+
console.log('ok');
1477+
" 2>/dev/null || echo 'failed')"
1478+
if [ "$ok" = "ok" ]; then
1479+
pass "OpenCode Zen alias 'zen' resolves to canonical 'opencode' provider id"
1480+
else
1481+
fail "T67 — $ok"
1482+
fi
1483+
fi
1484+
fi
1485+
1486+
# --- T68: detect-backends picks up OPENCODE_ZEN_API_KEY and emits managed/opencode
1487+
if [ -x "$DETECT" ]; then
1488+
FAKE_HOME="$TMP_ROOT/t68-home"; mkdir -p "$FAKE_HOME"
1489+
ok="$(env -i HOME="$FAKE_HOME" PATH="/usr/bin:/bin" OPENCODE_ZEN_API_KEY="x" "$DETECT" 2>/dev/null | node -e "
1490+
let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
1491+
try{const j=JSON.parse(d);
1492+
if(!j.managed||!j.managed.opencode){console.log('no-managed-block');process.exit(1)}
1493+
if(j.managed.opencode.key_set!==true){console.log('not-detected');process.exit(1)}
1494+
if(j.managed.opencode.env!=='OPENCODE_ZEN_API_KEY'){console.log('wrong-env');process.exit(1)}
1495+
if(j.recommendation!=='managed/opencode'){console.log('not-recommended:'+j.recommendation);process.exit(1)}
1496+
console.log('ok');
1497+
}catch(e){console.log('parse-fail:'+e.message)}
1498+
})" 2>/dev/null || echo 'failed')"
1499+
if [ "$ok" = "ok" ]; then
1500+
pass "detect-backends picks up OPENCODE_ZEN_API_KEY and recommends managed/opencode"
1501+
else
1502+
fail "T68 — $ok"
1503+
fi
1504+
fi
1505+
1506+
# --- T69: managed tier composes with all v0.10.x/v0.11.x agent flags
1507+
# (the entire flag surface should work on the new tier too)
1508+
if [ -x "$CONFIG" ]; then
1509+
T="$TMP_ROOT/t69"; mkdir -p "$T"
1510+
(cd "$T" && "$CONFIG" \
1511+
--tier managed --provider opencode --model gpt-5.5 \
1512+
--reviewer-tier managed --reviewer-provider opencode --reviewer-model claude-opus-4-7 \
1513+
--sandbox-test-writer --coder-temp 0.3 >/dev/null 2>&1) || true
1514+
if [ -f "$T/opencode.json" ]; then
1515+
ok="$(node -e "
1516+
const j=require('$T/opencode.json');
1517+
if(j.model!=='opencode/gpt-5.5'){console.log('coder-wrong');process.exit(1)}
1518+
if(j.agent.review.model!=='opencode/claude-opus-4-7'){console.log('rev-wrong:'+j.agent.review.model);process.exit(1)}
1519+
if(j.agent.build.temperature!==0.3){console.log('temp-wrong');process.exit(1)}
1520+
if(!j.agent['test-writer']){console.log('no-tw');process.exit(1)}
1521+
// Both coder and reviewer use same provider (opencode) — single provider block
1522+
if(Object.keys(j.provider).length!==1){console.log('expected-single-provider-got:'+Object.keys(j.provider).join(','));process.exit(1)}
1523+
console.log('ok');
1524+
" 2>/dev/null || echo 'failed')"
1525+
if [ "$ok" = "ok" ]; then
1526+
pass "managed/opencode composes with --reviewer-* / --sandbox-* / --*-temp (full v0.11.x surface)"
1527+
else
1528+
fail "T69 — $ok"
1529+
fi
1530+
fi
1531+
fi
1532+
14371533
echo ""
14381534
echo "=== Results: $PASS passed, $FAIL failed ==="
14391535
[ "$FAIL" -eq 0 ] || exit 1

tests/test-pick.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,8 @@ for combo in \
246246
"proprietary/anthropic:claude" \
247247
"proprietary/openai:gpt" \
248248
"proprietary/google_aistudio:gemini" \
249-
"proprietary/zai:glm"; do
249+
"proprietary/zai:glm" \
250+
"managed/opencode:gpt-5.5"; do
250251
combo_pair="${combo%%:*}"
251252
expected_substr="${combo##*:}"
252253
T="$TMP_ROOT/t12-${combo_pair//\//-}"; make_target "$T" "private_local/ollama"

0 commit comments

Comments
 (0)