@aexol/spectral 0.9.169 → 0.9.170

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.
@@ -40,19 +40,19 @@ export declare const visualizationStreamEnvelopeSchema: z.ZodObject<{
40
40
  checkpoint: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
41
41
  }, "strip", z.ZodTypeAny, {
42
42
  status: "error" | "ok";
43
+ phase: "structure" | "settled" | "shell" | "detail" | "narrative";
43
44
  streamId: string;
44
45
  sequence: number;
45
46
  frameType: "patch" | "checkpoint" | "final";
46
- phase: "structure" | "settled" | "shell" | "narrative" | "detail";
47
47
  message?: string | undefined;
48
48
  spec?: Record<string, unknown> | undefined;
49
49
  checkpoint?: Record<string, unknown> | undefined;
50
50
  }, {
51
51
  status: "error" | "ok";
52
+ phase: "structure" | "settled" | "shell" | "detail" | "narrative";
52
53
  streamId: string;
53
54
  sequence: number;
54
55
  frameType: "patch" | "checkpoint" | "final";
55
- phase: "structure" | "settled" | "shell" | "narrative" | "detail";
56
56
  message?: string | undefined;
57
57
  spec?: Record<string, unknown> | undefined;
58
58
  checkpoint?: Record<string, unknown> | undefined;
@@ -13,9 +13,10 @@
13
13
  * in its public GraphQL schema (`schema.graphql`); there is no
14
14
  * equivalent REST endpoint and we don't want to add one just for the
15
15
  * CLI.
16
- * - The query is parameterless and authenticated via the same Bearer
17
- * machine JWT used by `/v1/*`, so a single `fetch` call is enough —
18
- * no Zeus client setup required.
16
+ *
17
+ * The query is built with the generated Zeus client (`src/generated/zeus`)
18
+ * instead of a raw `fetch` + hand-written GraphQL string, so the selected
19
+ * fields and their result shape stay in lock-step with `schema.graphql`.
19
20
  *
20
21
  * Caching:
21
22
  * - In-memory TTL cache (default 5 min), keyed by `${backendUrl}|${jwt}`.
@@ -53,8 +54,6 @@ export interface AllowedModel {
53
54
  export interface FetchAllowedModelsOptions {
54
55
  backendUrl: string;
55
56
  machineJwt: string;
56
- /** Override for tests. */
57
- fetchImpl?: typeof fetch;
58
57
  /**
59
58
  * Bypass the in-memory cache for this call. Tests use this to assert
60
59
  * that the network is actually hit; production code never sets it.
@@ -1 +1 @@
1
- {"version":3,"file":"models-fetch.d.ts","sourceRoot":"","sources":["../../src/relay/models-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,YAAY;IAC3B,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAC;IAChB,gGAAgG;IAChG,QAAQ,EAAE,MAAM,CAAC;IACjB;yEACqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,sBAAsB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,2FAA2F;IAC3F,cAAc,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,8EAA8E;IAC9E,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,2FAA2F;IAC3F,eAAe,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,kDAAkD;AAClD,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AA0BD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,YAAY,EAAE,CAAC,CAyHzB"}
1
+ {"version":3,"file":"models-fetch.d.ts","sourceRoot":"","sources":["../../src/relay/models-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAQH,MAAM,WAAW,YAAY;IAC3B,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAC;IAChB,gGAAgG;IAChG,QAAQ,EAAE,MAAM,CAAC;IACjB;yEACqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,sBAAsB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,oEAAoE;IACpE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,2FAA2F;IAC3F,cAAc,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,8EAA8E;IAC9E,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC3B,2FAA2F;IAC3F,eAAe,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,kDAAkD;AAClD,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,YAAY,EAAE,CAAC,CA4FzB"}
@@ -13,9 +13,10 @@
13
13
  * in its public GraphQL schema (`schema.graphql`); there is no
14
14
  * equivalent REST endpoint and we don't want to add one just for the
15
15
  * CLI.
16
- * - The query is parameterless and authenticated via the same Bearer
17
- * machine JWT used by `/v1/*`, so a single `fetch` call is enough —
18
- * no Zeus client setup required.
16
+ *
17
+ * The query is built with the generated Zeus client (`src/generated/zeus`)
18
+ * instead of a raw `fetch` + hand-written GraphQL string, so the selected
19
+ * fields and their result shape stay in lock-step with `schema.graphql`.
19
20
  *
20
21
  * Caching:
21
22
  * - In-memory TTL cache (default 5 min), keyed by `${backendUrl}|${jwt}`.
@@ -23,12 +24,12 @@
23
24
  * creates many of them, so we don't want to hammer the GraphQL
24
25
  * endpoint on every reconnect.
25
26
  */
27
+ import { classifyGraphqlError, createBackendClient, scalars, } from "../generated/graphql-client.js";
26
28
  const cache = new Map();
27
29
  /** Test-only helper: drops every cached entry. */
28
30
  export function clearAllowedModelsCache() {
29
31
  cache.clear();
30
32
  }
31
- const QUERY = `query AvailableAgentModels { availableAgentModels { name provider userModelId agentEnabled creditInputPer1M creditOutputPer1M creditCachedInputPer1M creditCacheReadPer1M creditCacheWritePer1M contextWindow supportsImages supportsReasoning isDefault isVisionDefault } }`;
32
33
  /**
33
34
  * Fetch the whitelist of allowed base models. Throws on any failure with a
34
35
  * message tailored for an operator running `spectral serve` — the caller
@@ -44,93 +45,63 @@ export async function fetchAllowedModels(opts) {
44
45
  return hit.models;
45
46
  }
46
47
  }
47
- const fetchImpl = opts.fetchImpl ?? fetch;
48
48
  const url = `${opts.backendUrl.replace(/\/$/, "")}/graphql`;
49
- let res;
49
+ let rows;
50
50
  try {
51
- res = await fetchImpl(url, {
52
- method: "POST",
53
- headers: {
54
- Authorization: `Bearer ${opts.machineJwt}`,
55
- "Content-Type": "application/json",
56
- },
57
- body: JSON.stringify({ query: QUERY }),
51
+ const client = createBackendClient({
52
+ backendUrl: opts.backendUrl,
53
+ machineJwt: opts.machineJwt,
54
+ });
55
+ const result = await client("query", { scalars })({
56
+ availableAgentModels: [
57
+ {},
58
+ {
59
+ name: true,
60
+ provider: true,
61
+ userModelId: true,
62
+ agentEnabled: true,
63
+ creditInputPer1M: true,
64
+ creditOutputPer1M: true,
65
+ creditCachedInputPer1M: true,
66
+ creditCacheReadPer1M: true,
67
+ creditCacheWritePer1M: true,
68
+ contextWindow: true,
69
+ supportsImages: true,
70
+ supportsReasoning: true,
71
+ isDefault: true,
72
+ isVisionDefault: true,
73
+ },
74
+ ],
58
75
  });
76
+ rows = result.availableAgentModels;
59
77
  }
60
78
  catch (err) {
61
- const msg = err instanceof Error ? err.message : String(err);
79
+ const { message: msg } = classifyGraphqlError(err);
62
80
  throw new Error(`Failed to fetch allowed models from backend (${url}): ${msg}. ` +
63
81
  `Check SPECTRAL_BACKEND_URL and machine JWT.`);
64
82
  }
65
- if (!res.ok) {
66
- let detail = "";
67
- try {
68
- detail = await res.text();
69
- }
70
- catch {
71
- // ignore
72
- }
73
- throw new Error(`Failed to fetch allowed models from backend (HTTP ${res.status} ${res.statusText})` +
74
- (detail ? `: ${detail.slice(0, 500)}` : "") +
75
- ". Check SPECTRAL_BACKEND_URL and machine JWT.");
76
- }
77
- let payload;
78
- try {
79
- payload = (await res.json());
80
- }
81
- catch (err) {
82
- throw new Error(`Failed to fetch allowed models: backend returned non-JSON (${err instanceof Error ? err.message : String(err)})`);
83
- }
84
- if (payload.errors && payload.errors.length > 0) {
85
- const msg = payload.errors
86
- .map((e) => (typeof e?.message === "string" ? e.message : "unknown"))
87
- .join("; ");
88
- throw new Error(`Failed to fetch allowed models: GraphQL errors: ${msg}`);
89
- }
90
- const rows = payload.data?.availableAgentModels;
91
- if (!Array.isArray(rows)) {
92
- throw new Error(`Failed to fetch allowed models: response missing availableAgentModels array`);
93
- }
94
83
  const models = [];
95
- for (const row of rows) {
96
- const name = typeof row?.name === "string" ? row.name : null;
97
- const provider = typeof row?.provider === "string" ? row.provider : null;
98
- if (!name || !provider)
84
+ for (const row of rows ?? []) {
85
+ if (!row?.name || !row?.provider)
99
86
  continue; // skip malformed rows defensively
100
- const asOptionalNumber = (v) => typeof v === "number" ? v : v == null ? null : undefined;
101
- const contextWindow = typeof row?.contextWindow === "number" && row.contextWindow > 0
102
- ? row.contextWindow
103
- : null;
104
- const supportsImages = typeof row?.supportsImages === "boolean"
105
- ? row.supportsImages
106
- : null;
107
- const supportsReasoning = typeof row?.supportsReasoning === "boolean"
108
- ? row.supportsReasoning
109
- : null;
110
- const isDefault = typeof row?.isDefault === "boolean"
111
- ? row.isDefault
112
- : null;
113
- const isVisionDefault = typeof row?.isVisionDefault === "boolean"
114
- ? row.isVisionDefault
115
- : null;
116
87
  const model = {
117
- modelId: name,
118
- displayName: name,
119
- provider,
120
- creditInputPer1M: asOptionalNumber(row?.creditInputPer1M),
121
- creditOutputPer1M: asOptionalNumber(row?.creditOutputPer1M),
122
- creditCachedInputPer1M: asOptionalNumber(row?.creditCachedInputPer1M),
123
- creditCacheReadPer1M: asOptionalNumber(row?.creditCacheReadPer1M),
124
- creditCacheWritePer1M: asOptionalNumber(row?.creditCacheWritePer1M),
125
- contextWindow,
126
- supportsImages,
127
- supportsReasoning,
128
- isDefault,
129
- isVisionDefault,
88
+ modelId: row.name,
89
+ displayName: row.name,
90
+ provider: row.provider,
91
+ userModelId: typeof row.userModelId === "string" ? row.userModelId : undefined,
92
+ creditInputPer1M: row.creditInputPer1M ?? null,
93
+ creditOutputPer1M: row.creditOutputPer1M ?? null,
94
+ creditCachedInputPer1M: row.creditCachedInputPer1M ?? null,
95
+ creditCacheReadPer1M: row.creditCacheReadPer1M ?? null,
96
+ creditCacheWritePer1M: row.creditCacheWritePer1M ?? null,
97
+ contextWindow: typeof row.contextWindow === "number" && row.contextWindow > 0
98
+ ? row.contextWindow
99
+ : null,
100
+ supportsImages: row.supportsImages ?? null,
101
+ supportsReasoning: row.supportsReasoning ?? null,
102
+ isDefault: row.isDefault ?? null,
103
+ isVisionDefault: row.isVisionDefault ?? null,
130
104
  };
131
- if (typeof row?.userModelId === "string") {
132
- model.userModelId = row.userModelId;
133
- }
134
105
  models.push(model);
135
106
  }
136
107
  cache.set(key, { fetchedAt: Date.now(), ttlMs, models });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexol/spectral",
3
- "version": "0.9.169",
3
+ "version": "0.9.170",
4
4
  "description": "AI coding agent for Aexol with relay-based browser access.",
5
5
  "type": "module",
6
6
  "private": false,