toolnexus 0.25.0 → 0.26.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/client.ts CHANGED
@@ -227,6 +227,12 @@ export type MetricEvent =
227
227
  promptTokens: number
228
228
  completionTokens: number
229
229
  error?: string
230
+ /** L1: how many questions were asked. */
231
+ questions: number
232
+ /** L1: sha256 hex of `canonicalRequest(model, questions)` ("" when unencodable). */
233
+ questionsHash: string
234
+ /** True when the decision came from the classifier cache and no request was sent. */
235
+ cached: boolean
230
236
  }
231
237
  // `warning`, never `error`: a degenerate-criteria report is advisory, and a consumer that
232
238
  // counts "has an error" as a failure must not count this.
package/src/judge.ts CHANGED
@@ -12,8 +12,10 @@ import {
12
12
  Decision,
13
13
  type DecisionAnswer,
14
14
  type NoulCriteria,
15
- type Question,
15
+ type AnyQuestion,
16
16
  type RecordedDecision,
17
+ isRawAnswer,
18
+ raw as rawQ,
17
19
  noul as noulQ,
18
20
  choice as choiceQ,
19
21
  score as scoreQ,
@@ -25,7 +27,7 @@ import type { Request } from "./types.js"
25
27
  /** One named question of an ordered question list. */
26
28
  export interface NamedQuestion {
27
29
  name: string
28
- question: Question
30
+ question: AnyQuestion
29
31
  }
30
32
 
31
33
  /** `noul` question with a name. Name the state field it judges in `instructions`. */
@@ -41,9 +43,15 @@ export function score(name: string, instructions: string, levels: string[]): Nam
41
43
  return { name, question: scoreQ(instructions, levels) }
42
44
  }
43
45
 
46
+ /** A raw question with a name: a System One type the library does not model (e.g. `box`), sent
47
+ * as written. Its answer decodes to a raw answer `{type, value}`. */
48
+ export function raw(name: string, type: string, instructions: string, criteria?: unknown): NamedQuestion {
49
+ return { name, question: rawQ(type, instructions, criteria) }
50
+ }
51
+
44
52
  /** Ordered list -> the §8B questions map. A duplicate name is an error naming it. */
45
- export function questionMap(questions: readonly NamedQuestion[]): Record<string, Question> {
46
- const out: Record<string, Question> = {}
53
+ export function questionMap(questions: readonly NamedQuestion[]): Record<string, AnyQuestion> {
54
+ const out: Record<string, AnyQuestion> = {}
47
55
  for (const { name, question } of questions) {
48
56
  if (Object.prototype.hasOwnProperty.call(out, name)) throw new Error(`duplicate question name ${JSON.stringify(name)}`)
49
57
  out[name] = question
@@ -93,6 +101,13 @@ function noulBand(p: number, b: Bands): Band {
93
101
  }
94
102
 
95
103
  function wrap(a: DecisionAnswer, b: Bands): Answer {
104
+ if (isRawAnswer(a)) {
105
+ // An unmodelled answer type is never sure: there is no number to band.
106
+ const out = { ...(a as object), sure: false } as Answer
107
+ Object.defineProperty(out, "value", { value: () => 0, enumerable: false })
108
+ Object.defineProperty(out, "pick", { value: () => "", enumerable: false })
109
+ return out
110
+ }
96
111
  const extra =
97
112
  a.type === "noul"
98
113
  ? { band: noulBand(a.noul, b) }
package/src/serve.ts CHANGED
@@ -233,6 +233,13 @@ export async function startA2AServer(opts: StartServerOptions): Promise<ServeHan
233
233
  // per request (stateless — sessionIdGenerator undefined). Checked first so
234
234
  // `/mcp` is never shadowed by the A2A POST handler.
235
235
  if (mcp && req.url && (req.url === "/mcp" || req.url.startsWith("/mcp?"))) {
236
+ // Stateless profile: no server-initiated SSE stream exists, so only POST is
237
+ // served. Refuse anything else at once (§7C) instead of holding a GET open.
238
+ if (req.method !== "POST") {
239
+ res.writeHead(405, { Allow: "POST" })
240
+ res.end()
241
+ return
242
+ }
236
243
  void (async () => {
237
244
  const body = req.method === "POST" ? await readBody(req) : ""
238
245
  const parsed = body ? safeJson(body) : undefined