loki-mode 8.8.0 → 8.8.1
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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/run.sh +29 -0
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +44 -1
- package/loki-ts/data/model-pricing.json +35 -10
- package/loki-ts/dist/loki.js +3 -3
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/codex.sh +21 -3
- package/providers/model_catalog.json +20 -8
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 8 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v8.8.
|
|
6
|
+
# Loki Mode v8.8.1
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -469,4 +469,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
469
469
|
|
|
470
470
|
---
|
|
471
471
|
|
|
472
|
-
**v8.8.
|
|
472
|
+
**v8.8.1 | [Autonomi](https://www.autonomi.dev/) flagship product | ~410 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
8.8.
|
|
1
|
+
8.8.1
|
package/autonomy/run.sh
CHANGED
|
@@ -773,6 +773,35 @@ print(catalog["providers"]["claude"]["cli_aliases"].get(os.environ["_LOKI_SELECT
|
|
|
773
773
|
}
|
|
774
774
|
loki_apply_build_profile
|
|
775
775
|
|
|
776
|
+
# Default hang guard for EVERY build, not just simple-web.
|
|
777
|
+
#
|
|
778
|
+
# The two timeouts above are set inside loki_apply_build_profile(), which
|
|
779
|
+
# returns immediately unless LOKI_BUILD_PROFILE=simple-web. So on a normal
|
|
780
|
+
# build both resolved to 0, and 0 means no guard at all -- verified by running
|
|
781
|
+
# the deadline helper directly: `deadline.py 0 0 3 -- sleep 5` runs to
|
|
782
|
+
# completion unkilled. A provider that hung had nothing to stop it.
|
|
783
|
+
#
|
|
784
|
+
# IDLE only, and no retry. That is what keeps this compatible with the standing
|
|
785
|
+
# objection recorded above (search: "former invoke_with_timeout"), whose two
|
|
786
|
+
# reasons remain correct:
|
|
787
|
+
#
|
|
788
|
+
# 1. "No safe generous default" applies to a fixed TOTAL timeout, which
|
|
789
|
+
# cannot tell a long legitimate iteration from a hang. An idle timeout
|
|
790
|
+
# can: it measures silence, not duration. Verified both directions --
|
|
791
|
+
# `sleep 600` under a 120s idle cap dies, while a process emitting output
|
|
792
|
+
# every second survives indefinitely. A coding agent streams constantly;
|
|
793
|
+
# one silent for two minutes is not working.
|
|
794
|
+
# 2. "Wrong retry semantics" stands, so nothing here retries. The call is
|
|
795
|
+
# killed, and the existing failure path handles it. Re-running an agent
|
|
796
|
+
# that may have already edited files remains off the table.
|
|
797
|
+
#
|
|
798
|
+
# 7200s hard ceiling is a backstop against a process that streams forever
|
|
799
|
+
# without converging; the idle cap is the load-bearing guard. Both are
|
|
800
|
+
# overridable, and setting either to 0 restores the old unguarded behaviour.
|
|
801
|
+
: "${LOKI_PROVIDER_IDLE_TIMEOUT:=120}"
|
|
802
|
+
: "${LOKI_PROVIDER_CALL_TIMEOUT:=7200}"
|
|
803
|
+
export LOKI_PROVIDER_IDLE_TIMEOUT LOKI_PROVIDER_CALL_TIMEOUT
|
|
804
|
+
|
|
776
805
|
loki_background_services_enabled() {
|
|
777
806
|
! loki_is_supervised_simple_web
|
|
778
807
|
}
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -678,6 +678,37 @@ async def _push_loki_state_loop() -> None:
|
|
|
678
678
|
except (json.JSONDecodeError, KeyError):
|
|
679
679
|
pass
|
|
680
680
|
|
|
681
|
+
# Third source: the .loki/pids/ registry, which a
|
|
682
|
+
# CLI-started background run DOES write. Without this
|
|
683
|
+
# the dashboard reported STOPPED for a healthy build:
|
|
684
|
+
# `loki start` writes neither loki.pid nor session.json
|
|
685
|
+
# (run.sh only UPDATES session.json when it already
|
|
686
|
+
# exists), so both checks above failed and every such
|
|
687
|
+
# run fell through to "stopped" while it was actively
|
|
688
|
+
# working. Confirmed against a live build: STATUS.txt
|
|
689
|
+
# said BUILDING and iterations were advancing while the
|
|
690
|
+
# dashboard showed STOPPED with 0 agents.
|
|
691
|
+
#
|
|
692
|
+
# Liveness is proven with os.kill(pid, 0), never by the
|
|
693
|
+
# file's presence -- a stale entry from a crashed run
|
|
694
|
+
# must NOT read as alive, which is the same
|
|
695
|
+
# anti-stale rule BUG-NEW-006 established above.
|
|
696
|
+
if not _pid_alive:
|
|
697
|
+
try:
|
|
698
|
+
_pid_dir = loki_dir / "pids"
|
|
699
|
+
for _entry in _pid_dir.glob("*.json"):
|
|
700
|
+
_rec = _safe_json_read(_entry, {})
|
|
701
|
+
if _rec.get("kind") not in ("wrapper", "runner"):
|
|
702
|
+
continue
|
|
703
|
+
try:
|
|
704
|
+
os.kill(int(_rec.get("pid", 0)), 0)
|
|
705
|
+
except (ValueError, OSError, ProcessLookupError):
|
|
706
|
+
continue
|
|
707
|
+
_pid_alive = True
|
|
708
|
+
break
|
|
709
|
+
except OSError:
|
|
710
|
+
pass
|
|
711
|
+
|
|
681
712
|
status_str = raw.get("mode", "autonomous")
|
|
682
713
|
# Control files are the AUTHORITY, and they are checked
|
|
683
714
|
# first. dashboard-state.json's "mode" is written by the
|
|
@@ -2925,7 +2956,7 @@ def _provider_model_offers(provider: str) -> list[dict]:
|
|
|
2925
2956
|
Every other provider is offered the generic tiers (small/medium/high), which
|
|
2926
2957
|
are provider-independent, each annotated with the concrete model id the
|
|
2927
2958
|
catalog says that provider dispatches. That is what makes the picker read
|
|
2928
|
-
"medium -> gpt-5.
|
|
2959
|
+
"medium -> gpt-5.6-terra" on codex and "medium -> claude-sonnet-5" on claude
|
|
2929
2960
|
without the frontend knowing a single model id.
|
|
2930
2961
|
"""
|
|
2931
2962
|
if provider == "claude":
|
|
@@ -7267,6 +7298,15 @@ _DEFAULT_PRICING = {
|
|
|
7267
7298
|
"haiku": {"input": 1.00, "output": 5.00},
|
|
7268
7299
|
# OpenAI Codex
|
|
7269
7300
|
"gpt-5.3-codex": {"input": 1.50, "output": 12.00},
|
|
7301
|
+
# gpt-5.6 line: sol (high) / terra (medium, default) / luna (small).
|
|
7302
|
+
# UNVERIFIED RATES. The model IDs are confirmed against
|
|
7303
|
+
# developers.openai.com/api/docs/models, but OpenAI's published per-token
|
|
7304
|
+
# prices for this line were not, so these are placeholders scaled from the
|
|
7305
|
+
# gpt-5.3 rate. They drive a display estimate only, never a gate. Replace
|
|
7306
|
+
# from the pricing page; tools/probe-model-catalog.py is the refresh path.
|
|
7307
|
+
"gpt-5.6-sol": {"input": 2.50, "output": 20.00},
|
|
7308
|
+
"gpt-5.6-terra": {"input": 1.50, "output": 12.00},
|
|
7309
|
+
"gpt-5.6-luna": {"input": 0.50, "output": 4.00},
|
|
7270
7310
|
}
|
|
7271
7311
|
|
|
7272
7312
|
# Active pricing - starts with defaults, updated from .loki/pricing.json
|
|
@@ -7843,6 +7883,9 @@ _PROVIDER_LABELS = {
|
|
|
7843
7883
|
"sonnet": "Sonnet 5",
|
|
7844
7884
|
"haiku": "Haiku 4.5",
|
|
7845
7885
|
"gpt-5.3-codex": "GPT-5.3 Codex",
|
|
7886
|
+
"gpt-5.6-sol": "GPT-5.6 Sol",
|
|
7887
|
+
"gpt-5.6-terra": "GPT-5.6 Terra",
|
|
7888
|
+
"gpt-5.6-luna": "GPT-5.6 Luna",
|
|
7846
7889
|
}
|
|
7847
7890
|
|
|
7848
7891
|
# Display-only pricing notes, keyed by model. These annotate the pricing table in
|
|
@@ -1,13 +1,38 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
"$schema_version": 1,
|
|
3
|
+
"_comment": "Rolling pricing table consumed by loki-ts/src/runner/budget.ts. Update this file when Anthropic / OpenAI / others publish new prices; no code change required. Pricing is USD per 1 million tokens. Aliases (opus/sonnet/haiku) point to the latest model of that family per providers/model_catalog.json.",
|
|
4
|
+
"_updated": "2026-06-30",
|
|
5
|
+
"_source": "https://www.anthropic.com/pricing + provider docs. sonnet=claude-sonnet-5 (list $3/$15; intro $2/$10 through 2026-08-31). opus=claude-opus-4-8. codex=gpt-5.3-codex standard tier. Cache tiers: read 0.1x input, write 1.25x input (Anthropic + OpenAI published multipliers).",
|
|
6
|
+
"pricing": {
|
|
7
|
+
"fable": {
|
|
8
|
+
"input": 10.0,
|
|
9
|
+
"output": 50.0,
|
|
10
|
+
"cache_read": 1.0,
|
|
11
|
+
"cache_write": 12.5
|
|
12
|
+
},
|
|
13
|
+
"opus": {
|
|
14
|
+
"input": 5.0,
|
|
15
|
+
"output": 25.0,
|
|
16
|
+
"cache_read": 0.5,
|
|
17
|
+
"cache_write": 6.25
|
|
18
|
+
},
|
|
19
|
+
"sonnet": {
|
|
20
|
+
"input": 3.0,
|
|
21
|
+
"output": 15.0,
|
|
22
|
+
"cache_read": 0.3,
|
|
23
|
+
"cache_write": 3.75
|
|
24
|
+
},
|
|
25
|
+
"haiku": {
|
|
26
|
+
"input": 1.0,
|
|
27
|
+
"output": 5.0,
|
|
28
|
+
"cache_read": 0.1,
|
|
29
|
+
"cache_write": 1.25
|
|
30
|
+
},
|
|
31
|
+
"gpt-5.3-codex": {
|
|
32
|
+
"input": 1.75,
|
|
33
|
+
"output": 14.0,
|
|
34
|
+
"cache_read": 0.175,
|
|
35
|
+
"cache_write": 2.1875
|
|
12
36
|
}
|
|
37
|
+
}
|
|
13
38
|
}
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var __=Object.create;var{getPrototypeOf:f_,defineProperty:rK,getOwnPropertyNames:h_}=Object;var v_=Object.prototype.hasOwnProperty;function g_(Z){return this[Z]}var m_,u_,p_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?m_??=new WeakMap:u_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?__(f_(Z)):{};let K=X||!Z||!Z.__esModule?rK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of h_(Z))if(!v_.call(K,$))rK(K,$,{get:g_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var qQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var d_=(Z)=>Z;function c_(Z,X){this[Z]=d_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)rK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:c_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var e0=import.meta.require;var RO={};l0(RO,{lokiDir:()=>j0,homeLokiDir:()=>I4,findRepoRootForVersion:()=>eK,REPO_ROOT:()=>o0});import{resolve as n7,dirname as tK}from"path";import{fileURLToPath as l_}from"url";import{existsSync as GQ}from"fs";import{homedir as i_}from"os";function a_(){let Z=PO;for(let X=0;X<6;X++){if(GQ(n7(Z,"VERSION"))&&GQ(n7(Z,"autonomy/run.sh")))return Z;let Q=tK(Z);if(Q===Z)break;Z=Q}return n7(PO,"..","..","..")}function eK(Z){let X=Z;for(let Q=0;Q<6;Q++){if(GQ(n7(X,"VERSION"))&&GQ(n7(X,"autonomy/run.sh")))return X;let Y=tK(X);if(Y===X)break;X=Y}return n7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??n7(process.cwd(),".loki")}function I4(){return n7(i_(),".loki")}var PO,o0;var G8=p(()=>{PO=tK(l_(import.meta.url));o0=a_()});import{readFileSync as s_}from"fs";import{resolve as n_,dirname as o_}from"path";import{fileURLToPath as r_}from"url";function _3(){if(h5!==null)return h5;let Z="8.8.
|
|
2
|
+
var __=Object.create;var{getPrototypeOf:f_,defineProperty:rK,getOwnPropertyNames:h_}=Object;var v_=Object.prototype.hasOwnProperty;function g_(Z){return this[Z]}var m_,u_,p_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?m_??=new WeakMap:u_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?__(f_(Z)):{};let K=X||!Z||!Z.__esModule?rK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of h_(Z))if(!v_.call(K,$))rK(K,$,{get:g_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var qQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var d_=(Z)=>Z;function c_(Z,X){this[Z]=d_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)rK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:c_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var e0=import.meta.require;var RO={};l0(RO,{lokiDir:()=>j0,homeLokiDir:()=>I4,findRepoRootForVersion:()=>eK,REPO_ROOT:()=>o0});import{resolve as n7,dirname as tK}from"path";import{fileURLToPath as l_}from"url";import{existsSync as GQ}from"fs";import{homedir as i_}from"os";function a_(){let Z=PO;for(let X=0;X<6;X++){if(GQ(n7(Z,"VERSION"))&&GQ(n7(Z,"autonomy/run.sh")))return Z;let Q=tK(Z);if(Q===Z)break;Z=Q}return n7(PO,"..","..","..")}function eK(Z){let X=Z;for(let Q=0;Q<6;Q++){if(GQ(n7(X,"VERSION"))&&GQ(n7(X,"autonomy/run.sh")))return X;let Y=tK(X);if(Y===X)break;X=Y}return n7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??n7(process.cwd(),".loki")}function I4(){return n7(i_(),".loki")}var PO,o0;var G8=p(()=>{PO=tK(l_(import.meta.url));o0=a_()});import{readFileSync as s_}from"fs";import{resolve as n_,dirname as o_}from"path";import{fileURLToPath as r_}from"url";function _3(){if(h5!==null)return h5;let Z="8.8.1";if(typeof Z==="string"&&Z.length>0)return h5=Z,h5;try{let X=o_(r_(import.meta.url)),Q=eK(X);h5=s_(n_(Q,"VERSION"),"utf-8").trim()}catch{h5="unknown"}return h5}var h5=null;var HQ=p(()=>{G8()});var yO={};l0(yO,{runOrThrow:()=>Uf,run:()=>E0,readStreamCapped:()=>UQ,commandVersion:()=>Of,commandExists:()=>X9,ShellError:()=>Z$,MAX_STDOUT_BYTES:()=>SO});async function UQ(Z,X=SO){let Q=Z.getReader(),Y=new TextDecoder,J="",z=0;try{while(z<X){let{done:K,value:$}=await Q.read();if(K)break;if(!$)continue;if(z+=$.byteLength,z>X){let W=$.byteLength-(z-X);J+=Y.decode($.subarray(0,W),{stream:!0});break}J+=Y.decode($,{stream:!0})}J+=Y.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return J}async function E0(Z,X={}){let Q=Bun.spawn({cmd:[...Z],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),Y,J;if(X.timeoutMs&&X.timeoutMs>0)Y=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}J=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[z,K,$]=await Promise.all([UQ(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:z,stderr:K,exitCode:$}}finally{if(Y)clearTimeout(Y);if(J)clearTimeout(J)}}async function Uf(Z,X={}){let Q=await E0(Z,X);if(Q.exitCode!==0)throw new Z$(`command failed (${Q.exitCode}): ${Z.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function X9(Z){let X=Bf(Z),Q=await E0(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function Bf(Z){if(!/^[A-Za-z0-9._/-]+$/.test(Z))throw Error(`refused to shell-escape suspect token: ${Z}`);return Z}async function Of(Z,X="--version"){if(!await X9(Z))return null;let Y=await E0([Z,X],{timeoutMs:5000});if(Y.exitCode!==0)return null;return((Y.stdout||Y.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var SO=16777216,Z$;var x9=p(()=>{Z$=class Z$ extends Error{message;exitCode;stdout;stderr;constructor(Z,X,Q,Y){super(Z);this.message=Z;this.exitCode=X;this.stdout=Q;this.stderr=Y;this.name="ShellError"}}});function o7(Z){return Lf?"":Z}var Lf,L0,k8,p0,rW0,i0,H8,Q9,v;var S6=p(()=>{Lf=(process.env.NO_COLOR??"").length>0;L0=o7("\x1B[0;31m"),k8=o7("\x1B[0;32m"),p0=o7("\x1B[1;33m"),rW0=o7("\x1B[0;34m"),i0=o7("\x1B[0;36m"),H8=o7("\x1B[1m"),Q9=o7("\x1B[2m"),v=o7("\x1B[0m")});import{existsSync as Pf}from"fs";async function D7(){if(R4!==void 0)return R4;let Z="/opt/homebrew/bin/python3.12";if(Pf(Z))return R4=Z,Z;let X=await X9("python3.12");if(X)return R4=X,X;let Q=await X9("python3");return R4=Q,Q}async function E7(Z,X={}){let Q=await D7();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return E0([Q,"-c",Z],X)}var R4;var r7=p(()=>{x9()});var eO={};l0(eO,{runStatus:()=>Xh});import{existsSync as Y9,readFileSync as h3,readdirSync as lO,statSync as iO}from"fs";import{resolve as h8,basename as lf}from"path";import{homedir as af}from"os";function aO(Z){let X=Math.trunc(Z);if(X>=1e6)return`${(Math.trunc(X/1e6*10)/10).toFixed(1)}M`;if(X>=1000)return`${(Math.trunc(X/1000*10)/10).toFixed(1)}K`;return String(X)}function sO(Z,X,Q){if(X===0)return null;let Y=Math.trunc(Z*100/X),J=Math.trunc(Z*NQ/X);if(J>NQ)J=NQ;let z=NQ-J,K=k8;if(Y>=80)K=L0;else if(Y>=50)K=p0;let $="=".repeat(Math.max(0,J))+" ".repeat(Math.max(0,z)),W=aO(Z),V=aO(X);return` ${H8}${Q}${v} ${K}[${$}]${v} ${Y}% (${W} / ${V})`}async function nf(){if(await X9("jq"))return!0;return process.stdout.write(`${L0}Error: jq is required but not installed.${v}
|
|
3
3
|
`),process.stdout.write(`Install with:
|
|
4
4
|
`),process.stdout.write(` brew install jq (macOS)
|
|
5
5
|
`),process.stdout.write(` apt install jq (Debian/Ubuntu)
|
|
@@ -434,7 +434,7 @@ Start a session with: loki start <prd>`}}let J=Kh(Y);return{exitCode:0,stdout:X?
|
|
|
434
434
|
`),0}async function Eh(Z){let X=!1;for(let Q of Z)if(Q==="--json")X=!0;else if(Q==="--help"||Q==="-h")return Fh(),0;else return process.stderr.write(`${L0}Unknown option: ${Q}${v}
|
|
435
435
|
`),process.stderr.write(`Usage: loki doctor [--json]
|
|
436
436
|
`),1;if(X){let Q=await BL();return process.stdout.write(JSON.stringify(Q,null,2)+`
|
|
437
|
-
`),0}return Dh()}var Uh,MQ,Oh,Lh,Th=90;var OL=p(()=>{G8();x9();r7();S6();HQ();Uh=/(\d+\.\d+(?:\.\d+)*)/;MQ={fn:U$};Oh=[{name:"Claude Code",dir:".claude/skills/loki-mode"},{name:"Codex CLI",dir:".codex/skills/loki-mode"},{name:"Cline CLI",dir:".cline/skills/loki-mode"},{name:"Aider CLI",dir:".aider/skills/loki-mode"}];Lh=[{displayName:"Node.js (>= 18)",jsonName:"Node.js",cmd:"node",required:"required",min:"18.0"},{displayName:"Python 3 (>= 3.8)",jsonName:"Python 3",cmd:"python3",required:"required",min:"3.8"},{displayName:"jq",jsonName:"jq",cmd:"jq",required:"required"},{displayName:"git",jsonName:"git",cmd:"git",required:"required"},{displayName:"curl",jsonName:"curl",cmd:"curl",required:"required"},{displayName:"bash (>= 4.0)",jsonName:"bash",cmd:"bash",required:"recommended",min:"4.0"},{displayName:"Bun (>= 1.3)",jsonName:"Bun",cmd:"bun",required:"recommended",min:"1.3"},{displayName:"Claude CLI",jsonName:"Claude CLI",cmd:"claude",required:"optional"},{displayName:"Codex CLI",jsonName:"Codex CLI",cmd:"codex",required:"optional"},{displayName:"Cline CLI",jsonName:"Cline CLI",cmd:"cline",required:"optional"},{displayName:"Aider CLI",jsonName:"Aider CLI",cmd:"aider",required:"optional"}]});var TL={};l0(TL,{writeBudgetState:()=>j$,readEfficiencyDir:()=>wQ,readBudgetState:()=>fh,parseRetryAfter:()=>uh,isRateLimited:()=>mh,checkBudgetLimitForRunner:()=>dh,checkBudgetLimit:()=>ML,calculateRateLimitBackoff:()=>ph,calculateCostFromRecords:()=>TQ,PRICING:()=>A$});import{existsSync as M$,mkdirSync as O$,readdirSync as Ih,readFileSync as T$,renameSync as Ph,writeFileSync as L$}from"fs";import{dirname as w$,join as Z5,resolve as Rh}from"path";import{fileURLToPath as kh}from"url";function xh(){try{let Z=w$(kh(import.meta.url)),X=Rh(Z,"..","..","data","model-pricing.json");if(!M$(X))return S4;let Y=JSON.parse(T$(X,"utf8")).pricing;if(!Y||typeof Y!=="object")return S4;let J={};for(let[z,K]of Object.entries(Y))if(K!==null&&typeof K==="object"&&typeof K.input==="number"&&typeof K.output==="number")J[z]={input:K.input,output:K.output};for(let z of Object.keys(S4))if(!(z in J))return S4;return J}catch{return S4}}function yh(Z){return Math.round((Z+Number.EPSILON)*1e4)/1e4}function bh(Z){let X=(Z??LL).toLowerCase();return A$[X]??A$[LL]}function TQ(Z){let X=0;for(let Q of Z){if(typeof Q.cost_usd==="number"&&Number.isFinite(Q.cost_usd)){X+=Q.cost_usd;continue}let Y=bh(Q.model),J=typeof Q.input_tokens==="number"?Q.input_tokens:0,z=typeof Q.output_tokens==="number"?Q.output_tokens:0;X+=J/1e6*Y.input+z/1e6*Y.output}return yh(X)}function wQ(Z){if(!M$(Z))return[];let X=[],Q;try{Q=Ih(Z)}catch{return[]}for(let Y of Q){if(!Y.endsWith(".json"))continue;let J=Z5(Z,Y);try{let z=T$(J,"utf8"),K=JSON.parse(z);if(K&&typeof K==="object")X.push(K)}catch{}}return X}function _h(Z){if(Z===null||typeof Z!=="object"||Array.isArray(Z))return!1;let X=Z;if(typeof X.limit!=="number"||!Number.isFinite(X.limit))return!1;if(typeof X.budget_limit!=="number"||!Number.isFinite(X.budget_limit))return!1;if(typeof X.budget_used!=="number"||!Number.isFinite(X.budget_used))return!1;if(typeof X.exceeded!=="boolean")return!1;if(X.exceeded_at!==void 0&&typeof X.exceeded_at!=="string")return!1;if(X.created_at!==void 0&&typeof X.created_at!=="string")return!1;return!0}function fh(Z){let X=Z??Z5(j0(),"metrics","budget.json");if(!M$(X))return null;try{let Q=T$(X,"utf8"),Y=JSON.parse(Q);if(!_h(Y))return console.warn(`[budget] discarding malformed budget state at ${X}`),null;return Y}catch{return null}}function j$(Z,X){let Q=X??Z5(j0(),"metrics","budget.json");O$(w$(Q),{recursive:!0});let Y=[];if(Y.push("{"),Y.push(` "limit": ${N$(Z.limit)},`),Y.push(` "budget_limit": ${N$(Z.budget_limit)},`),Y.push(` "budget_used": ${N$(Z.budget_used)},`),Z.exceeded){Y.push(' "exceeded": true,');let K=Z.exceeded_at??jL();Y.push(` "exceeded_at": "${K}"`)}else Y.push(' "exceeded": false');Y.push("}");let J=Y.join(`
|
|
437
|
+
`),0}return Dh()}var Uh,MQ,Oh,Lh,Th=90;var OL=p(()=>{G8();x9();r7();S6();HQ();Uh=/(\d+\.\d+(?:\.\d+)*)/;MQ={fn:U$};Oh=[{name:"Claude Code",dir:".claude/skills/loki-mode"},{name:"Codex CLI",dir:".codex/skills/loki-mode"},{name:"Cline CLI",dir:".cline/skills/loki-mode"},{name:"Aider CLI",dir:".aider/skills/loki-mode"}];Lh=[{displayName:"Node.js (>= 18)",jsonName:"Node.js",cmd:"node",required:"required",min:"18.0"},{displayName:"Python 3 (>= 3.8)",jsonName:"Python 3",cmd:"python3",required:"required",min:"3.8"},{displayName:"jq",jsonName:"jq",cmd:"jq",required:"required"},{displayName:"git",jsonName:"git",cmd:"git",required:"required"},{displayName:"curl",jsonName:"curl",cmd:"curl",required:"required"},{displayName:"bash (>= 4.0)",jsonName:"bash",cmd:"bash",required:"recommended",min:"4.0"},{displayName:"Bun (>= 1.3)",jsonName:"Bun",cmd:"bun",required:"recommended",min:"1.3"},{displayName:"Claude CLI",jsonName:"Claude CLI",cmd:"claude",required:"optional"},{displayName:"Codex CLI",jsonName:"Codex CLI",cmd:"codex",required:"optional"},{displayName:"Cline CLI",jsonName:"Cline CLI",cmd:"cline",required:"optional"},{displayName:"Aider CLI",jsonName:"Aider CLI",cmd:"aider",required:"optional"}]});var TL={};l0(TL,{writeBudgetState:()=>j$,readEfficiencyDir:()=>wQ,readBudgetState:()=>fh,parseRetryAfter:()=>uh,isRateLimited:()=>mh,checkBudgetLimitForRunner:()=>dh,checkBudgetLimit:()=>ML,calculateRateLimitBackoff:()=>ph,calculateCostFromRecords:()=>TQ,PRICING:()=>A$});import{existsSync as M$,mkdirSync as O$,readdirSync as Ih,readFileSync as T$,renameSync as Ph,writeFileSync as L$}from"fs";import{dirname as w$,join as Z5,resolve as Rh}from"path";import{fileURLToPath as kh}from"url";function xh(){try{let Z=w$(kh(import.meta.url)),X=Rh(Z,"..","..","data","model-pricing.json");if(!M$(X))return S4;let Y=JSON.parse(T$(X,"utf8")).pricing;if(!Y||typeof Y!=="object")return S4;let J={};for(let[z,K]of Object.entries(Y))if(K!==null&&typeof K==="object"&&typeof K.input==="number"&&typeof K.output==="number")J[z]={input:K.input,output:K.output};for(let z of Object.keys(S4))if(!(z in J))return S4;return J}catch{return S4}}function yh(Z){return Math.round((Z+Number.EPSILON)*1e4)/1e4}function bh(Z){let X=(Z??LL).toLowerCase();return A$[X]??A$[LL]}function TQ(Z){let X=0;for(let Q of Z){if(typeof Q.cost_usd==="number"&&Number.isFinite(Q.cost_usd)){X+=Q.cost_usd;continue}let Y=bh(Q.model),J=typeof Q.input_tokens==="number"?Q.input_tokens:0,z=typeof Q.output_tokens==="number"?Q.output_tokens:0,K=typeof Q.cache_read_tokens==="number"?Q.cache_read_tokens:0,$=typeof Q.cache_creation_tokens==="number"?Q.cache_creation_tokens:0,W=Y.cache_read??Y.input,V=Y.cache_write??Y.input;X+=J/1e6*Y.input+z/1e6*Y.output+K/1e6*W+$/1e6*V}return yh(X)}function wQ(Z){if(!M$(Z))return[];let X=[],Q;try{Q=Ih(Z)}catch{return[]}for(let Y of Q){if(!Y.endsWith(".json"))continue;let J=Z5(Z,Y);try{let z=T$(J,"utf8"),K=JSON.parse(z);if(K&&typeof K==="object")X.push(K)}catch{}}return X}function _h(Z){if(Z===null||typeof Z!=="object"||Array.isArray(Z))return!1;let X=Z;if(typeof X.limit!=="number"||!Number.isFinite(X.limit))return!1;if(typeof X.budget_limit!=="number"||!Number.isFinite(X.budget_limit))return!1;if(typeof X.budget_used!=="number"||!Number.isFinite(X.budget_used))return!1;if(typeof X.exceeded!=="boolean")return!1;if(X.exceeded_at!==void 0&&typeof X.exceeded_at!=="string")return!1;if(X.created_at!==void 0&&typeof X.created_at!=="string")return!1;return!0}function fh(Z){let X=Z??Z5(j0(),"metrics","budget.json");if(!M$(X))return null;try{let Q=T$(X,"utf8"),Y=JSON.parse(Q);if(!_h(Y))return console.warn(`[budget] discarding malformed budget state at ${X}`),null;return Y}catch{return null}}function j$(Z,X){let Q=X??Z5(j0(),"metrics","budget.json");O$(w$(Q),{recursive:!0});let Y=[];if(Y.push("{"),Y.push(` "limit": ${N$(Z.limit)},`),Y.push(` "budget_limit": ${N$(Z.budget_limit)},`),Y.push(` "budget_used": ${N$(Z.budget_used)},`),Z.exceeded){Y.push(' "exceeded": true,');let K=Z.exceeded_at??jL();Y.push(` "exceeded_at": "${K}"`)}else Y.push(' "exceeded": false');Y.push("}");let J=Y.join(`
|
|
438
438
|
`)+`
|
|
439
439
|
`,z=`${Q}.tmp.${process.pid}`;L$(z,J),Ph(z,Q)}function N$(Z){if(Number.isInteger(Z))return Z.toString();return Z.toString()}function jL(Z=new Date){return Z.toISOString().replace(/\.\d{3}Z$/,"Z")}function hh(Z){if(Z===null||Z===void 0||Z==="")return null;if(typeof Z==="number")return Number.isFinite(Z)?Z:null;let X=Z.replace(/[^0-9.]/g,"");if(!X)return null;let Q=Number(X);return Number.isFinite(Q)?Q:null}function ML(Z={}){let X=j0(),Q=hh(Z.budgetLimit??process.env.BUDGET_LIMIT??null);if(Q===null)return{exceeded:!1,warn:!1,current_cost:0,limit:null};let Y=Z.efficiencyDir??Z5(X,"metrics","efficiency"),J=Z.budgetFile??Z5(X,"metrics","budget.json"),z=Z.pauseFile??Z5(X,"PAUSE"),K=Z.signalsDir??Z5(X,"signals"),$=Z.now??(()=>new Date),W=wQ(Y),V=TQ(W);if(V>=Q){let H=jL($());O$(w$(z),{recursive:!0}),L$(z,""),O$(K,{recursive:!0});let B={type:"BUDGET_EXCEEDED",limit:Q,current:V,timestamp:H};return L$(Z5(K,"BUDGET_EXCEEDED"),JSON.stringify(B)),j$({limit:Q,budget_limit:Q,budget_used:V,exceeded:!0,exceeded_at:H},J),{exceeded:!0,warn:!1,current_cost:V,limit:Q}}if(V>0)j$({limit:Q,budget_limit:Q,budget_used:V,exceeded:!1},J);return{exceeded:!1,warn:V>=Sh*Q,current_cost:V,limit:Q}}function mh(Z){let X=Array.isArray(Z)?Z.join(`
|
|
440
440
|
`):Z;if(!X)return!1;if(vh.test(X))return!0;if(gh.test(X))return!0;return!1}function uh(Z){if(!Z)return 0;AL.lastIndex=0;let X=0,Q;while((Q=AL.exec(Z))!==null){let Y=Q[1];if(Y!==void 0){let J=Number.parseInt(Y,10);if(Number.isFinite(J))X=J}}return X}function ph(Z,X){if(typeof Z==="number"&&Z>0)return Z;let Q=typeof X==="number"&&X>0?X:50,Y=Math.floor(7200/Q);if(Y<60)Y=60;if(Y>300)Y=300;return Y}async function dh(Z){return ML({budgetLimit:Z.budgetLimit,iteration:Z.iterationCount,efficiencyDir:`${Z.lokiDir}/metrics/efficiency`,budgetFile:`${Z.lokiDir}/metrics/budget.json`,pauseFile:`${Z.lokiDir}/PAUSE`,signalsDir:`${Z.lokiDir}/signals`}).exceeded}var S4,A$,LL="sonnet",Sh=0.8,vh,gh,AL;var C$=p(()=>{G8();S4={fable:{input:10,output:50},opus:{input:5,output:25},sonnet:{input:3,output:15},haiku:{input:1,output:5},"gpt-5.3-codex":{input:1.5,output:12}};A$=Object.freeze(xh());vh=/(429|rate.?limit|too many requests|quota exceeded|request limit|retry.?after)/i,gh=/resets [0-9]+[ap]m/,AL=/retry.?after:?\s*([0-9]+)/gi});import{existsSync as CQ,readdirSync as ch,readFileSync as lh,statSync as ih}from"fs";import{join as FQ}from"path";function ah(Z){let X=[],Q=FQ(Z,"votes");if(!CQ(Q))return X;let Y;try{Y=ch(Q)}catch{return X}for(let J of Y){if(!J.startsWith("round-")||!J.endsWith(".json"))continue;try{let z=FQ(Q,J);if(!ih(z).isFile())continue;let K=JSON.parse(lh(z,"utf8"));X.push({iteration:typeof K.iteration==="number"?K.iteration:void 0,verdict:typeof K.verdict==="string"?K.verdict:void 0,complete_votes:typeof K.complete_votes==="number"?K.complete_votes:void 0,total_members:typeof K.total_members==="number"?K.total_members:void 0,threshold:typeof K.threshold==="number"?K.threshold:void 0})}catch{}}return X}function sh(){return{iteration_count:0,total_cost_usd:0,avg_cost_per_iteration:null,total_input_tokens:0,total_output_tokens:0,total_duration_ms:0,avg_duration_ms_per_iteration:null,model_breakdown:{},phase_breakdown:{},status_breakdown:{}}}function nh(){return{council_rounds:0,unanimous_rate:null,approval_rate:null,iteration_success_rate:null}}function oh(Z){let X=sh();if(Z.length===0)return X;X.iteration_count=Z.length,X.total_cost_usd=Math.round(TQ(Z)*1e4)/1e4;for(let Q of Z){if(typeof Q.input_tokens==="number")X.total_input_tokens+=Q.input_tokens;if(typeof Q.output_tokens==="number")X.total_output_tokens+=Q.output_tokens;let Y=Q;if(typeof Y.duration_ms==="number")X.total_duration_ms+=Y.duration_ms;if(typeof Q.model==="string")X.model_breakdown[Q.model]=(X.model_breakdown[Q.model]??0)+1;if(typeof Y.phase==="string")X.phase_breakdown[Y.phase]=(X.phase_breakdown[Y.phase]??0)+1;if(typeof Y.status==="string")X.status_breakdown[Y.status]=(X.status_breakdown[Y.status]??0)+1}return X.avg_cost_per_iteration=Math.round(X.total_cost_usd/X.iteration_count*1e4)/1e4,X.avg_duration_ms_per_iteration=Math.round(X.total_duration_ms/X.iteration_count),X}function rh(Z,X,Q){let Y=nh();if(Y.council_rounds=Z.length,Z.length>0){let J=0,z=0;for(let K of Z){if(typeof K.complete_votes==="number"&&typeof K.total_members==="number"&&K.total_members>0&&K.complete_votes===K.total_members)J+=1;if(K.verdict==="COMPLETE")z+=1}Y.unanimous_rate=Math.round(J/Z.length*1e4)/1e4,Y.approval_rate=Math.round(z/Z.length*1e4)/1e4}if(Q>0)Y.iteration_success_rate=Math.round(X/Q*1e4)/1e4;return Y}function wL(Z){let X=[],Q=FQ(Z,"metrics","efficiency"),Y=FQ(Z,"council"),J=CQ(Q)?wQ(Q):[];if(!CQ(Q))X.push("no .loki/metrics/efficiency/ dir (efficiency KPIs zeroed)");else if(J.length===0)X.push(".loki/metrics/efficiency/ exists but no iteration files found");let z=ah(Y);if(!CQ(Y))X.push("no .loki/council/ dir (accuracy KPIs zeroed)");else if(z.length===0)X.push(".loki/council/ exists but no round-N.json files found");let K=oh(J),$=K.status_breakdown.success??0,W=rh(z,$,K.iteration_count);return{schema_version:1,generated_at:new Date().toISOString(),loki_dir:Z,efficiency:K,accuracy:W,notes:X}}function CL(Z){return JSON.stringify(Z,null,2)}function FL(Z){let X=[];X.push(`Loki Mode KPIs (snapshot at ${Z.generated_at})`),X.push(`Source: ${Z.loki_dir}`),X.push(""),X.push("Efficiency"),X.push(` Iterations: ${Z.efficiency.iteration_count}`),X.push(` Total cost USD: ${Z.efficiency.total_cost_usd}`),X.push(` Avg cost per iter: ${Z.efficiency.avg_cost_per_iteration??"n/a"}`),X.push(` Total input tokens: ${Z.efficiency.total_input_tokens}`),X.push(` Total output tokens: ${Z.efficiency.total_output_tokens}`),X.push(` Total duration (ms): ${Z.efficiency.total_duration_ms}`),X.push(` Avg duration / iter: ${Z.efficiency.avg_duration_ms_per_iteration??"n/a"}`);let Q=Object.entries(Z.efficiency.model_breakdown).sort((z,K)=>z[0].localeCompare(K[0]));if(Q.length>0)X.push(` Model breakdown: ${Q.map(([z,K])=>`${z}=${K}`).join(", ")}`);let Y=Object.entries(Z.efficiency.phase_breakdown).sort((z,K)=>z[0].localeCompare(K[0]));if(Y.length>0)X.push(` Phase breakdown: ${Y.map(([z,K])=>`${z}=${K}`).join(", ")}`);let J=Object.entries(Z.efficiency.status_breakdown).sort((z,K)=>z[0].localeCompare(K[0]));if(J.length>0)X.push(` Status breakdown: ${J.map(([z,K])=>`${z}=${K}`).join(", ")}`);if(X.push(""),X.push("Accuracy"),X.push(` Council rounds: ${Z.accuracy.council_rounds}`),X.push(` Unanimous rate: ${Z.accuracy.unanimous_rate??"n/a"}`),X.push(` Approval rate: ${Z.accuracy.approval_rate??"n/a"}`),X.push(` Iter success rate: ${Z.accuracy.iteration_success_rate??"n/a"}`),Z.notes.length>0){X.push(""),X.push("Notes");for(let z of Z.notes)X.push(` - ${z}`)}return X.push(""),X.push("See also: loki trust (trust trajectory across runs)"),X.join(`
|
|
@@ -1220,4 +1220,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
1220
1220
|
`),2}case"start":{let{runStart:Y}=await Promise.resolve().then(() => (S_(),x_));return Y(Q)}default:return process.stderr.write(`Unknown command: ${X}
|
|
1221
1221
|
`),process.stderr.write(y_),2}}dO();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var yW0=await SW0(Bun.argv.slice(2));process.exit(yW0);
|
|
1222
1222
|
|
|
1223
|
-
//# debugId=
|
|
1223
|
+
//# debugId=CC7FBAFAA3CFFD5964756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "8.8.
|
|
4
|
+
"version": "8.8.1",
|
|
5
5
|
"description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 8 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "loki-mode",
|
|
4
4
|
"displayName": "Loki Mode",
|
|
5
|
-
"version": "8.8.
|
|
5
|
+
"version": "8.8.1",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|
package/providers/codex.sh
CHANGED
|
@@ -132,9 +132,27 @@ _codex_validate_model() {
|
|
|
132
132
|
# fallback is validated, since it may carry Claude aliases (opus/sonnet/haiku)
|
|
133
133
|
# that are invalid for Codex. Validating the whole chain silently downgraded a
|
|
134
134
|
# trusted LOKI_CODEX_MODEL to the default (BUG-PROV-003 fix).
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
#
|
|
136
|
+
# Each tier resolves from the catalog, which is the single source of truth for
|
|
137
|
+
# what "small / medium / high" means per provider. Before this, all three tiers
|
|
138
|
+
# fell back to CODEX_DEFAULT_MODEL (empty), so a codex run sent no --model at
|
|
139
|
+
# all and `LOKI_SESSION_MODEL=high` selected nothing -- the tier vocabulary was
|
|
140
|
+
# inert on codex and a user had to name a model by hand.
|
|
141
|
+
#
|
|
142
|
+
# The empty default is still the last resort: if the catalog lookup yields
|
|
143
|
+
# nothing, we send no --model and let Codex pick, which is the safe outcome for
|
|
144
|
+
# a ChatGPT-account user rather than a guessed name.
|
|
145
|
+
_codex_tier_model() {
|
|
146
|
+
local tier="$1" resolved=""
|
|
147
|
+
if command -v loki_latest_model >/dev/null 2>&1; then
|
|
148
|
+
resolved="$(loki_latest_model codex "$tier" 2>/dev/null)"
|
|
149
|
+
fi
|
|
150
|
+
printf '%s' "${resolved:-$CODEX_DEFAULT_MODEL}"
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
PROVIDER_MODEL_PLANNING="${LOKI_CODEX_MODEL:-$(_codex_validate_model "${LOKI_MODEL_PLANNING:-$(_codex_tier_model planning)}")}"
|
|
154
|
+
PROVIDER_MODEL_DEVELOPMENT="${LOKI_CODEX_MODEL:-$(_codex_validate_model "${LOKI_MODEL_DEVELOPMENT:-$(_codex_tier_model development)}")}"
|
|
155
|
+
PROVIDER_MODEL_FAST="${LOKI_CODEX_MODEL:-$(_codex_validate_model "${LOKI_MODEL_FAST:-$(_codex_tier_model fast)}")}"
|
|
138
156
|
|
|
139
157
|
# Effort levels (Codex-specific: maps to reasoning time, not model capability)
|
|
140
158
|
PROVIDER_EFFORT_PLANNING="xhigh"
|
|
@@ -49,17 +49,29 @@
|
|
|
49
49
|
]
|
|
50
50
|
},
|
|
51
51
|
"codex": {
|
|
52
|
-
"latest_planning": "gpt-5.
|
|
53
|
-
"latest_development": "gpt-5.
|
|
54
|
-
"latest_fast": "gpt-5.
|
|
55
|
-
"tier_fallback": {
|
|
56
|
-
"development": "planning",
|
|
57
|
-
"fast": "planning"
|
|
58
|
-
},
|
|
52
|
+
"latest_planning": "gpt-5.6-sol",
|
|
53
|
+
"latest_development": "gpt-5.6-terra",
|
|
54
|
+
"latest_fast": "gpt-5.6-luna",
|
|
59
55
|
"models": [
|
|
56
|
+
{
|
|
57
|
+
"id": "gpt-5.6-sol",
|
|
58
|
+
"tier": "planning",
|
|
59
|
+
"notes": "high tier. OpenAI: 'Frontier model for complex professional work'. Verified against developers.openai.com/api/docs/models."
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "gpt-5.6-terra",
|
|
63
|
+
"tier": "development",
|
|
64
|
+
"notes": "medium tier, the DEFAULT. OpenAI: balances performance with affordability."
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "gpt-5.6-luna",
|
|
68
|
+
"tier": "fast",
|
|
69
|
+
"notes": "small tier. OpenAI: budget-conscious, high-volume."
|
|
70
|
+
},
|
|
60
71
|
{
|
|
61
72
|
"id": "gpt-5.3-codex",
|
|
62
|
-
"tier": "
|
|
73
|
+
"tier": "legacy",
|
|
74
|
+
"notes": "Prior default, superseded by the gpt-5.6 line. Kept discoverable, not dispatched."
|
|
63
75
|
},
|
|
64
76
|
{
|
|
65
77
|
"id": "o3",
|