@aibridge/cli 0.2.0 → 0.4.0

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/src/index.ts CHANGED
@@ -6,9 +6,6 @@ export { getDriver } from './drivers.ts';
6
6
  export {
7
7
  type Backend,
8
8
  backendModelId,
9
- DEFAULT_IMAGE_GEN,
10
- DEFAULT_IMPLEMENTER,
11
- DEFAULT_MODEL,
12
9
  type Effort,
13
10
  formatImageGenModelError,
14
11
  formatUnknownModelError,
package/src/models.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Models are registered by canonical, provider-qualified slug —
5
5
  * `<vendor>-<cli>/<model>[-<effort>]`, e.g. `openai-codex/gpt-5.6-sol-high`.
6
6
  * Canonical slugs only — no short aliases, by design. That holds on both sides:
7
- * `backendModel` is a pinned model id (`claude-opus-5`), never a moving vendor
7
+ * `backendModel` is a pinned model id (`claude-sonnet-5`), never a moving vendor
8
8
  * alias (`opus`), so a seat never silently changes model under you.
9
9
  */
10
10
 
@@ -31,7 +31,7 @@ export const MODELS: Record<string, ModelSpec> = {
31
31
  backend: 'grok',
32
32
  backendModel: 'grok-4.5',
33
33
  efforts: ['low', 'medium', 'high'],
34
- brief: 'xAI Grok 4.5 via grok CLI — default for plan & review; own xAI login',
34
+ brief: 'xAI Grok 4.5 via grok CLI — own xAI login; ~30 req/min, ~1k msgs/day, single-flight',
35
35
  },
36
36
  'google-antigravity/gemini-3.6-flash': {
37
37
  slug: 'google-antigravity/gemini-3.6-flash',
@@ -40,7 +40,17 @@ export const MODELS: Record<string, ModelSpec> = {
40
40
  efforts: ['low', 'medium', 'high'],
41
41
  defaultEffort: 'high',
42
42
  brief:
43
- 'Google Gemini 3.6 Flash via agy — default for implement, also image-gen; own Antigravity login',
43
+ 'Google Gemini 3.6 Flash via agy — own Antigravity login; quota shared across all Gemini tiers',
44
+ },
45
+ 'google-antigravity/gemini-3.1-pro': {
46
+ slug: 'google-antigravity/gemini-3.1-pro',
47
+ backend: 'agy',
48
+ // agy exposes only -high and -low for this class — there is no medium tier.
49
+ efforts: ['low', 'high'],
50
+ backendModel: 'gemini-3.1-pro',
51
+ defaultEffort: 'high',
52
+ brief:
53
+ 'Google Gemini 3.1 Pro via agy — own Antigravity login; quota shared across all Gemini tiers',
44
54
  },
45
55
  'google-antigravity/claude-sonnet-4-6': {
46
56
  slug: 'google-antigravity/claude-sonnet-4-6',
@@ -68,37 +78,56 @@ export const MODELS: Record<string, ModelSpec> = {
68
78
  backend: 'codex',
69
79
  backendModel: 'gpt-5.6-sol',
70
80
  efforts: ['low', 'medium', 'high', 'xhigh'],
71
- brief: 'OpenAI Codex gpt-5.6-sol via codex CLI',
81
+ brief: 'OpenAI gpt-5.6-sol via codex CLI — frontier agentic coding; own ChatGPT login',
72
82
  },
73
- 'anthropic-claude/sonnet-5': {
74
- slug: 'anthropic-claude/sonnet-5',
83
+ 'openai-codex/gpt-5.6-terra': {
84
+ slug: 'openai-codex/gpt-5.6-terra',
85
+ backend: 'codex',
86
+ backendModel: 'gpt-5.6-terra',
87
+ efforts: ['low', 'medium', 'high', 'xhigh'],
88
+ brief: 'OpenAI gpt-5.6-terra via codex CLI — balanced, everyday coding; own ChatGPT login',
89
+ },
90
+ 'openai-codex/gpt-5.6-luna': {
91
+ slug: 'openai-codex/gpt-5.6-luna',
92
+ backend: 'codex',
93
+ backendModel: 'gpt-5.6-luna',
94
+ efforts: ['low', 'medium', 'high', 'xhigh'],
95
+ brief: 'OpenAI gpt-5.6-luna via codex CLI — fast and affordable coding; own ChatGPT login',
96
+ },
97
+ 'anthropic-claude/fable-5': {
98
+ slug: 'anthropic-claude/fable-5',
75
99
  backend: 'claude',
76
- backendModel: 'claude-sonnet-5',
100
+ backendModel: 'claude-fable-5',
77
101
  efforts: ['low', 'medium', 'high', 'xhigh', 'max'],
78
- brief: 'Claude Sonnet 5 via claude CLI — bills your Claude subscription',
102
+ defaultEffort: 'high',
103
+ brief:
104
+ 'Claude Fable 5 via claude CLI — hardest, longest-running work; bills the claude CLI subscription',
79
105
  },
80
106
  'anthropic-claude/opus-5': {
81
107
  slug: 'anthropic-claude/opus-5',
82
108
  backend: 'claude',
83
- backendModel: 'claude-opus-5',
109
+ backendModel: 'claude-opus-5[1m]',
84
110
  efforts: ['low', 'medium', 'high', 'xhigh', 'max'],
85
111
  defaultEffort: 'high',
86
- brief: 'Claude Opus 5 via claude CLI (default effort: high) — bills subscription',
112
+ brief:
113
+ 'Claude Opus 5, 1M context via claude CLI — everyday complex work; bills the claude CLI subscription',
87
114
  },
88
- 'anthropic-claude/opus-5-1m': {
89
- slug: 'anthropic-claude/opus-5-1m',
115
+ 'anthropic-claude/sonnet-5': {
116
+ slug: 'anthropic-claude/sonnet-5',
90
117
  backend: 'claude',
91
- backendModel: 'claude-opus-5[1m]',
118
+ backendModel: 'claude-sonnet-5',
92
119
  efforts: ['low', 'medium', 'high', 'xhigh', 'max'],
93
- defaultEffort: 'high',
94
- brief: 'Claude Opus 5, 1M context via claude CLI — long-context work; bills subscription',
120
+ brief: 'Claude Sonnet 5 via claude CLI — routine work; bills the claude CLI subscription',
121
+ },
122
+ 'anthropic-claude/haiku-4-5': {
123
+ slug: 'anthropic-claude/haiku-4-5',
124
+ backend: 'claude',
125
+ backendModel: 'claude-haiku-4-5-20251001',
126
+ efforts: ['low', 'medium', 'high', 'xhigh', 'max'],
127
+ brief: 'Claude Haiku 4.5 via claude CLI — quick answers; bills the claude CLI subscription',
95
128
  },
96
129
  } as const satisfies Record<string, ModelSpec>;
97
130
 
98
- export const DEFAULT_MODEL = 'xai-grok/grok-4.5';
99
- export const DEFAULT_IMPLEMENTER = 'google-antigravity/gemini-3.6-flash';
100
- export const DEFAULT_IMAGE_GEN = 'openai-codex/gpt-5.6-sol';
101
-
102
131
  export type ImageFormat = 'jpg' | 'png';
103
132
 
104
133
  const IMAGE_GEN_FORMATS: ReadonlyMap<Backend, ImageFormat> = new Map([
@@ -1,8 +1,13 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import type { AgyQuotaSnapshot } from '@aibridge/driver-agy';
3
3
  import type { CodexQuotaSnapshot } from '@aibridge/driver-codex';
4
+ import type { GrokQuotaSnapshot } from '@aibridge/driver-grok';
4
5
  import { test } from 'vitest';
5
- import { evaluateAgyPreflight, evaluateCodexPreflight } from './quotaPreflight.ts';
6
+ import {
7
+ evaluateAgyPreflight,
8
+ evaluateCodexPreflight,
9
+ evaluateGrokPreflight,
10
+ } from './quotaPreflight.ts';
6
11
 
7
12
  test('evaluateAgyPreflight: exhausted model returns ok:false with resetTime', () => {
8
13
  const snapshot: AgyQuotaSnapshot = {
@@ -176,3 +181,43 @@ test('evaluateCodexPreflight: healthy (77% used) returns ok:true', () => {
176
181
 
177
182
  assert.deepEqual(result, { ok: true });
178
183
  });
184
+
185
+ test('evaluateGrokPreflight: usedPercent at 100 refuses with resetAt', () => {
186
+ const snapshot: GrokQuotaSnapshot = {
187
+ fetchedAt: '2024-01-01T12:00:00Z',
188
+ periodType: 'weekly',
189
+ periodStart: '2024-01-01T00:00:00Z',
190
+ periodEnd: '2024-01-08T00:00:00Z',
191
+ usedPercent: 100,
192
+ products: [],
193
+ onDemandUsedCents: undefined,
194
+ onDemandCapCents: undefined,
195
+ prepaidBalanceCents: undefined,
196
+ };
197
+
198
+ const result = evaluateGrokPreflight(snapshot);
199
+
200
+ assert.deepEqual(result, {
201
+ ok: false,
202
+ message: 'grok credit quota exhausted',
203
+ resetAt: '2024-01-08T00:00:00Z',
204
+ });
205
+ });
206
+
207
+ test('evaluateGrokPreflight: healthy (17% used) returns ok:true', () => {
208
+ const snapshot: GrokQuotaSnapshot = {
209
+ fetchedAt: '2024-01-01T12:00:00Z',
210
+ periodType: 'weekly',
211
+ periodStart: '2024-01-01T00:00:00Z',
212
+ periodEnd: '2024-01-08T00:00:00Z',
213
+ usedPercent: 17,
214
+ products: [],
215
+ onDemandUsedCents: undefined,
216
+ onDemandCapCents: undefined,
217
+ prepaidBalanceCents: undefined,
218
+ };
219
+
220
+ const result = evaluateGrokPreflight(snapshot);
221
+
222
+ assert.deepEqual(result, { ok: true });
223
+ });
@@ -1,5 +1,6 @@
1
1
  import { type AgyQuotaSnapshot, fetchAgyQuota, findModelQuota } from '@aibridge/driver-agy';
2
2
  import { type CodexQuotaSnapshot, fetchCodexQuota } from '@aibridge/driver-codex';
3
+ import { fetchGrokQuota, type GrokQuotaSnapshot } from '@aibridge/driver-grok';
3
4
  import { backendModelId, type ResolvedModel } from './models.ts';
4
5
 
5
6
  export type PreflightVerdict =
@@ -51,11 +52,27 @@ export function evaluateCodexPreflight(snapshot: CodexQuotaSnapshot): PreflightV
51
52
  return { ok: true };
52
53
  }
53
54
 
55
+ export function evaluateGrokPreflight(snapshot: GrokQuotaSnapshot): PreflightVerdict {
56
+ if (snapshot.usedPercent !== undefined && snapshot.usedPercent >= 100) {
57
+ return {
58
+ ok: false,
59
+ message: 'grok credit quota exhausted',
60
+ resetAt: snapshot.periodEnd,
61
+ };
62
+ }
63
+
64
+ return { ok: true };
65
+ }
66
+
54
67
  export async function preflightModel(resolved: ResolvedModel): Promise<PreflightVerdict> {
55
68
  if (resolved.spec.backend === 'codex') {
56
69
  return preflightCodex();
57
70
  }
58
71
 
72
+ if (resolved.spec.backend === 'grok') {
73
+ return preflightGrok();
74
+ }
75
+
59
76
  if (resolved.spec.backend !== 'agy') {
60
77
  return { ok: true };
61
78
  }
@@ -83,6 +100,18 @@ export async function preflightCodex(): Promise<PreflightVerdict> {
83
100
  }
84
101
  }
85
102
 
103
+ export async function preflightGrok(): Promise<PreflightVerdict> {
104
+ try {
105
+ const snapshot = await fetchGrokQuota();
106
+ return evaluateGrokPreflight(snapshot);
107
+ } catch (err) {
108
+ return {
109
+ ok: true,
110
+ warning: `quota preflight failed (${(err as Error).message}); proceeding`,
111
+ };
112
+ }
113
+ }
114
+
86
115
  function formatReset(resetTime: string | undefined): string {
87
116
  if (!resetTime) return '-';
88
117
  const ms = new Date(resetTime).getTime() - Date.now();