@aexol/spectral 0.3.0 → 0.3.2

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.
@@ -234,6 +234,17 @@ export class RelayClient extends EventEmitter {
234
234
  this.exit(1);
235
235
  return;
236
236
  }
237
+ // Machine was revoked by the team admin from the Aexol Studio panel
238
+ // (backend sets `KnownMachine.revokedAt` and closes the socket with
239
+ // 4001 "machine-revoked"). Exit immediately — the machine JWT is now
240
+ // invalid and re-registration requires a fresh `spectral serve`.
241
+ if (code === 4001 && reasonStr === "machine-revoked") {
242
+ this.logger.error("\n✗ This machine has been disconnected from the Aexol Studio panel.");
243
+ this.logger.error(" Run `spectral serve` again to re-register.\n");
244
+ this.dispose();
245
+ this.exit(1);
246
+ return;
247
+ }
237
248
  this.scheduleReconnect();
238
249
  });
239
250
  }
@@ -30,6 +30,7 @@ const KNOWN_KEYS = new Set([
30
30
  "machineJwt",
31
31
  "teamId",
32
32
  "ownerId",
33
+ "visibility",
33
34
  "registeredAt",
34
35
  "hostname",
35
36
  "version",
@@ -79,6 +80,7 @@ export async function loadMachine() {
79
80
  machineJwt: parsed.machineJwt,
80
81
  teamId: typeof parsed.teamId === "string" ? parsed.teamId : undefined,
81
82
  ownerId: typeof parsed.ownerId === "string" ? parsed.ownerId : undefined,
83
+ visibility: typeof parsed.visibility === "string" ? parsed.visibility : undefined,
82
84
  registeredAt: parsed.registeredAt,
83
85
  hostname: parsed.hostname,
84
86
  version: parsed.version,
@@ -109,6 +111,8 @@ export async function saveMachine(rec) {
109
111
  toWrite.teamId = rec.teamId;
110
112
  if (rec.ownerId !== undefined)
111
113
  toWrite.ownerId = rec.ownerId;
114
+ if (rec.visibility !== undefined)
115
+ toWrite.visibility = rec.visibility;
112
116
  if (rec.extra) {
113
117
  for (const [k, v] of Object.entries(rec.extra))
114
118
  toWrite[k] = v;
@@ -131,6 +131,7 @@ export async function ensureMachineRegistered(deps) {
131
131
  machineJwt: obj.jwt,
132
132
  teamId: typeof obj.teamId === "string" ? obj.teamId : undefined,
133
133
  ownerId: typeof obj.ownerId === "string" ? obj.ownerId : undefined,
134
+ visibility: typeof obj.visibility === "string" ? obj.visibility : undefined,
134
135
  registeredAt: Date.now(),
135
136
  hostname: hostname(),
136
137
  version: deps.version,
@@ -124,6 +124,12 @@ const MODEL_PRICING = [
124
124
  { prefix: "claude-3-sonnet", input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.30 },
125
125
  { prefix: "claude-3-haiku", input: 0.25, output: 1.25, cacheWrite: 1.25, cacheRead: 0.025 },
126
126
  // OpenAI models
127
+ { prefix: "gpt-5.5", input: 1.75, output: 14, cacheWrite: 14, cacheRead: 0.35 },
128
+ { prefix: "gpt-5.4", input: 1.75, output: 14, cacheWrite: 14, cacheRead: 0.35 },
129
+ { prefix: "gpt-5.3", input: 1.75, output: 14, cacheWrite: 14, cacheRead: 0.35 },
130
+ { prefix: "gpt-5.2", input: 1.75, output: 14, cacheWrite: 14, cacheRead: 0.35 },
131
+ { prefix: "gpt-5.1", input: 2, output: 8, cacheWrite: 8, cacheRead: 0.50 },
132
+ { prefix: "gpt-5", input: 1.25, output: 10, cacheWrite: 10, cacheRead: 0.25 },
127
133
  { prefix: "gpt-4.1", input: 2, output: 8, cacheWrite: 8, cacheRead: 0.50 },
128
134
  { prefix: "gpt-4o", input: 2.50, output: 10, cacheWrite: 10, cacheRead: 1.25 },
129
135
  { prefix: "gpt-4-turbo", input: 10, output: 30, cacheWrite: 0, cacheRead: 0 },
@@ -144,10 +150,20 @@ const MODEL_PRICING = [
144
150
  { prefix: "meta-llama/llama-4", input: 0.20, output: 0.80, cacheWrite: 0, cacheRead: 0 },
145
151
  { prefix: "meta-llama/llama-3.3", input: 0.20, output: 0.50, cacheWrite: 0, cacheRead: 0 },
146
152
  ];
153
+ /**
154
+ * Strip vendor prefix to extract the bare model name.
155
+ * e.g. "openai/gpt-5.3-codex" → "gpt-5.3-codex".
156
+ * Returns the original string when no separator is present.
157
+ */
158
+ function bareModelId(modelId) {
159
+ const idx = modelId.lastIndexOf("/");
160
+ return idx === -1 ? modelId : modelId.slice(idx + 1);
161
+ }
147
162
  /** Look up pricing for a modelId. Returns null when unknown. */
148
163
  function lookupPricing(modelId) {
164
+ const bare = bareModelId(modelId);
149
165
  for (const entry of MODEL_PRICING) {
150
- if (modelId.startsWith(entry.prefix)) {
166
+ if (modelId.startsWith(entry.prefix) || bare.startsWith(entry.prefix)) {
151
167
  return {
152
168
  input: entry.input,
153
169
  output: entry.output,
@@ -158,6 +174,22 @@ function lookupPricing(modelId) {
158
174
  }
159
175
  return null;
160
176
  }
177
+ /**
178
+ * Model prefixes known to support reasoning/thinking.
179
+ * Mirrors pi-ai's supportsXhigh() + additional models.
180
+ */
181
+ const REASONING_SUPPORT_PREFIXES = [
182
+ "gpt-5.2", "gpt-5.3", "gpt-5.4", "gpt-5.5",
183
+ "claude-opus-4",
184
+ "o3", "o4",
185
+ "deepseek-r1",
186
+ "gemini-2.5",
187
+ ];
188
+ /** Check if a modelId prefix indicates reasoning/thinking support. */
189
+ function supportsReasoning(modelId) {
190
+ const bare = bareModelId(modelId);
191
+ return REASONING_SUPPORT_PREFIXES.some((p) => modelId.startsWith(p) || bare.startsWith(p));
192
+ }
161
193
  /**
162
194
  * Parse the newline-delimited JSON of wire events persisted alongside an
163
195
  * assistant message and extract all tool_call / tool_result events.
@@ -497,7 +529,7 @@ export class PiBridge {
497
529
  // at our synthetic proxy provider so auth resolves to the machine JWT.
498
530
  provider: SPECTRAL_PROXY_ANTHROPIC,
499
531
  baseUrl,
500
- reasoning: false,
532
+ reasoning: supportsReasoning(m.modelId),
501
533
  input: ["text", "image"],
502
534
  // Real pricing so pi can compute accurate token costs.
503
535
  cost: pricing
@@ -527,7 +559,7 @@ export class PiBridge {
527
559
  // breaking auth lookup against our synthetic proxy provider.
528
560
  provider: SPECTRAL_PROXY_OPENAI,
529
561
  baseUrl,
530
- reasoning: false,
562
+ reasoning: supportsReasoning(m.modelId),
531
563
  input: ["text", "image"],
532
564
  // Real pricing so pi can compute accurate token costs.
533
565
  cost: pricing
@@ -558,7 +590,7 @@ export class PiBridge {
558
590
  api: "openai-completions",
559
591
  provider: SPECTRAL_PROXY_USER_MODEL,
560
592
  baseUrl,
561
- reasoning: false,
593
+ reasoning: supportsReasoning(m.modelId),
562
594
  input: ["text", "image"],
563
595
  cost: pricing
564
596
  ? { input: pricing.input, output: pricing.output, cacheRead: pricing.cacheRead, cacheWrite: pricing.cacheWrite }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexol/spectral",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Always-on coding agent for Aexol — branded pi wrapper with relay-based browser access.",
5
5
  "type": "module",
6
6
  "private": false,