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/README.md +35 -0
- package/dist/classifier.d.ts +189 -31
- package/dist/classifier.js +589 -30
- package/dist/client.d.ts +6 -0
- package/dist/judge.d.ts +6 -3
- package/dist/judge.js +13 -1
- package/dist/serve.js +7 -0
- package/package.json +2 -2
- package/src/classifier.ts +677 -46
- package/src/client.ts +6 -0
- package/src/judge.ts +19 -4
- package/src/serve.ts +7 -0
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
|
|
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:
|
|
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,
|
|
46
|
-
const out: Record<string,
|
|
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
|