@zanii/blackbox 0.16.1 → 0.18.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.
@@ -232,6 +232,27 @@ export const FAULTS = {
232
232
  en: "A tool's definition changed mid-session",
233
233
  ar: "تغيّر تعريف أداة أثناء الجلسة",
234
234
  },
235
+ USER_DOWNVOTE: {
236
+ fault: "BBX-4601",
237
+ en: "The person served said an answer was wrong",
238
+ ar: "قال الشخص المخدوم إن الإجابة خاطئة",
239
+ },
240
+ SECRET_SENT: {
241
+ fault: "BBX-9106",
242
+ en: "A credential was sent to a model or tool",
243
+ ar: "أُرسلت بيانات اعتماد إلى نموذج أو أداة",
244
+ },
245
+ PROMPT_INJECTION: {
246
+ fault: "BBX-7403",
247
+ squawk: "HIJACK",
248
+ en: "Prompt injection reached the model",
249
+ ar: "وصلت محاولة حقن تعليمات إلى النموذج",
250
+ },
251
+ TOOL_DRIFTED: {
252
+ fault: "BBX-7404",
253
+ en: "A tool's definition differs from earlier sessions",
254
+ ar: "يختلف تعريف أداة عمّا رأته الجلسات السابقة",
255
+ },
235
256
  REVOKED_MEMORY_READ: {
236
257
  fault: "BBX-7101",
237
258
  en: "A revoked memory was used",
@@ -45,6 +45,12 @@ export declare class Evidence {
45
45
  private pairSums;
46
46
  }
47
47
  /** The final answers on a record, each with the evidence it should rest on. */
48
+ /**
49
+ * spec/findings.md §9: what the answer's own web search returned (Anthropic's server tool): each
50
+ * result's title, and each citation's quoted text and title. The provider supplies these from the
51
+ * pages, not the model, so a fact they hold is backed. Never the answer's own words.
52
+ */
53
+ export declare function webEvidence(c: Call): string;
48
54
  export declare function answersOf(lines: readonly string[], bodies: Bodies): Array<{
49
55
  seq: number;
50
56
  text: string;
@@ -460,6 +460,30 @@ export class Evidence {
460
460
  }
461
461
  }
462
462
  /** The final answers on a record, each with the evidence it should rest on. */
463
+ /**
464
+ * spec/findings.md §9: what the answer's own web search returned (Anthropic's server tool): each
465
+ * result's title, and each citation's quoted text and title. The provider supplies these from the
466
+ * pages, not the model, so a fact they hold is backed. Never the answer's own words.
467
+ */
468
+ export function webEvidence(c) {
469
+ const parts = [];
470
+ const add = (v) => {
471
+ if (typeof v === "string" && v)
472
+ parts.push(v);
473
+ };
474
+ for (const [, b] of [...c.blocks].sort(([x], [y]) => x - y)) {
475
+ const block = b;
476
+ if (block.type === "web_search_tool_result" && Array.isArray(block.content))
477
+ for (const r of block.content)
478
+ add(r?.title);
479
+ if (Array.isArray(block.citations))
480
+ for (const cit of block.citations) {
481
+ add(cit?.cited_text);
482
+ add(cit?.title);
483
+ }
484
+ }
485
+ return parts.join("\n");
486
+ }
463
487
  export function answersOf(lines, bodies) {
464
488
  const calls = callsOf(lines, bodies);
465
489
  const ends = new Set(toolCallsOf(calls).map((t) => t.seq));
@@ -480,9 +504,11 @@ export function answersOf(lines, bodies) {
480
504
  const text = answerText(c);
481
505
  if (!text)
482
506
  continue;
507
+ const web = webEvidence(c);
483
508
  const pieces = [
484
509
  { seq: c.requestSeq, text: canonical(c.request) },
485
510
  ...results.filter((r) => r.seq < c.endSeq),
511
+ ...(web ? [{ seq: c.endSeq, text: web }] : []),
486
512
  ];
487
513
  out.push({
488
514
  seq: c.endSeq,
@@ -82,6 +82,20 @@ export declare function exitDurability(events: Ev[]): Finding[];
82
82
  * is the reference; a different one later is a finding, and becomes the new reference.
83
83
  */
84
84
  export declare function toolChanged(events: Ev[]): Finding[];
85
+ /**
86
+ * spec/findings.md §1a: a tool's definition differs from the one this tenant's earlier sessions saw
87
+ * (a rug pull between sessions). The gateway puts the previous hashes next to a `tools/list` answer
88
+ * (`meta.tool_defs_prev`); a tool listed with another hash is a finding, once per server and tool.
89
+ */
90
+ export declare function toolDrifted(events: Ev[]): Finding[];
91
+ /**
92
+ * spec/data.md §6a: a credential left for a model or a tool. The record's own redaction counted it
93
+ * (`meta.redacted.secrets`); what the model was sent had fewer masked (`meta.outbound_redacted`), or
94
+ * the call went to an MCP tool. A request refused for it (`outbound_blocked`) sent nothing.
95
+ */
96
+ export declare function secretSent(events: Ev[]): Finding[];
97
+ /** spec/findings.md §12b: a person the agent served said an answer was wrong (👎), once per answer. */
98
+ export declare function userDownvotes(events: Ev[]): Finding[];
85
99
  /** N6 (idea S3): the gateway refused a request that carried the session's own token. */
86
100
  export declare function tokenMisuse(events: Ev[]): Finding[];
87
101
  /** Stage 2 R3: a `verify` check that passed, then failed later in the run. */
@@ -14,6 +14,7 @@ import { wallFindings } from "../walls/index.js";
14
14
  import { detectors } from "./detectors.js";
15
15
  import { groundingFindings, judgeFindings } from "./grounding.js";
16
16
  import { toolHallucinations } from "./hallucination.js";
17
+ import { injectionFindings } from "./injection.js";
17
18
  import { flightPlan } from "./landing.js";
18
19
  import { memoryFindings } from "./memory.js";
19
20
  export { detectorAccuracy, wilsonLow } from "./accuracy.js";
@@ -56,6 +57,10 @@ export function analyze(lines, options) {
56
57
  ...orphanedToolCalls(events, now),
57
58
  ...tokenMisuse(events),
58
59
  ...toolChanged(events),
60
+ ...toolDrifted(events),
61
+ ...secretSent(events),
62
+ ...userDownvotes(events),
63
+ ...injectionFindings(lines, options.bodies),
59
64
  ...verifyRegression(events),
60
65
  ...falseSuccess(events, now),
61
66
  ...toolHallucinations(lines, options.bodies),
@@ -401,6 +406,105 @@ export function toolChanged(events) {
401
406
  }
402
407
  return out;
403
408
  }
409
+ /**
410
+ * spec/findings.md §1a: a tool's definition differs from the one this tenant's earlier sessions saw
411
+ * (a rug pull between sessions). The gateway puts the previous hashes next to a `tools/list` answer
412
+ * (`meta.tool_defs_prev`); a tool listed with another hash is a finding, once per server and tool.
413
+ */
414
+ export function toolDrifted(events) {
415
+ const out = [];
416
+ const done = new Set();
417
+ for (const e of events) {
418
+ const { server, tool_defs: defs, tool_defs_prev: prev } = e.meta;
419
+ if (e.kind !== "tool.result" || typeof server !== "string")
420
+ continue;
421
+ if (!defs || typeof defs !== "object" || Array.isArray(defs))
422
+ continue;
423
+ if (!prev || typeof prev !== "object" || Array.isArray(prev))
424
+ continue;
425
+ for (const tool of Object.keys(defs).sort()) {
426
+ const after = defs[tool];
427
+ const before = prev[tool];
428
+ const key = `${server}\u0000${tool}`;
429
+ if (typeof after !== "string" || typeof before !== "string" || before === after)
430
+ continue;
431
+ if (done.has(key))
432
+ continue;
433
+ done.add(key);
434
+ out.push({
435
+ code: "TOOL_DRIFTED",
436
+ source: "detectors",
437
+ severity: "warning",
438
+ ref: { seq: e.seq, server, tool, before, after },
439
+ detail: "A tool's description, schema or annotations differ from what this tenant's earlier sessions saw: the tool was changed between runs.",
440
+ });
441
+ }
442
+ }
443
+ return out;
444
+ }
445
+ /**
446
+ * spec/data.md §6a: a credential left for a model or a tool. The record's own redaction counted it
447
+ * (`meta.redacted.secrets`); what the model was sent had fewer masked (`meta.outbound_redacted`), or
448
+ * the call went to an MCP tool. A request refused for it (`outbound_blocked`) sent nothing.
449
+ */
450
+ export function secretSent(events) {
451
+ const blocked = new Set();
452
+ for (const e of events)
453
+ if (e.kind === "llm.incomplete" &&
454
+ e.meta.reason === "outbound_blocked" &&
455
+ typeof e.meta.request_seq === "number")
456
+ blocked.add(e.meta.request_seq);
457
+ const count = (v) => {
458
+ const n = v?.secrets;
459
+ return typeof n === "number" && Number.isSafeInteger(n) && n > 0 ? n : 0;
460
+ };
461
+ const out = [];
462
+ for (const e of events) {
463
+ let to = null;
464
+ let n = 0;
465
+ if (e.kind === "llm.request" && !blocked.has(e.seq)) {
466
+ n = count(e.meta.redacted) - count(e.meta.outbound_redacted);
467
+ to = "model";
468
+ }
469
+ else if (e.kind === "tool.call" && typeof e.meta.tool === "string") {
470
+ n = count(e.meta.redacted);
471
+ to = "tool";
472
+ }
473
+ if (!to || n <= 0)
474
+ continue;
475
+ out.push({
476
+ code: "SECRET_SENT",
477
+ source: "detectors",
478
+ severity: "warning",
479
+ ref: { seq: e.seq, to, count: n, ...(to === "tool" ? { tool: e.meta.tool } : {}) },
480
+ detail: to === "model"
481
+ ? "A credential (a key, a token, a password) went to the model provider in the request."
482
+ : "A credential (a key, a token, a password) went to a tool in its arguments; the gateway's credential broker keeps them out of the agent's hands.",
483
+ });
484
+ }
485
+ return out;
486
+ }
487
+ /** spec/findings.md §12b: a person the agent served said an answer was wrong (👎), once per answer. */
488
+ export function userDownvotes(events) {
489
+ const out = [];
490
+ const seen = new Set();
491
+ for (const e of events) {
492
+ const about = e.meta.about_seq;
493
+ if (e.kind !== "control" || e.meta.action !== "feedback" || e.meta.rating !== "down")
494
+ continue;
495
+ if (typeof about !== "number" || seen.has(about))
496
+ continue;
497
+ seen.add(about);
498
+ out.push({
499
+ code: "USER_DOWNVOTE",
500
+ source: "detectors",
501
+ severity: "advisory",
502
+ ref: { seq: about, feedback_seq: e.seq },
503
+ detail: "The person this answer was for said it was wrong.",
504
+ });
505
+ }
506
+ return out;
507
+ }
404
508
  /** N6 (idea S3): the gateway refused a request that carried the session's own token. */
405
509
  export function tokenMisuse(events) {
406
510
  return events
@@ -0,0 +1,22 @@
1
+ /** The rules, in the order they're tried. Patterns run on ASCII-lowercased, space-folded text. */
2
+ export declare const INJECTION_RULES: ReadonlyArray<{
3
+ id: string;
4
+ re: RegExp;
5
+ }>;
6
+ /** Text no reader sees: Unicode tag characters, bidi overrides, a run of 4+ zero-width characters. */
7
+ export declare function hiddenText(text: string): boolean;
8
+ /** The first rule a text breaks, or null. */
9
+ export declare function injectionRule(text: string): string | null;
10
+ /** spec/findings.md §1b: PROMPT_INJECTION findings over a record. */
11
+ export declare function injectionFindings(lines: readonly string[], bodies: (hash: string) => Uint8Array | undefined): {
12
+ code: string;
13
+ source: string;
14
+ severity: "warning";
15
+ ref: {
16
+ seq: number;
17
+ source: string;
18
+ rule: string;
19
+ tool?: string;
20
+ };
21
+ detail: string;
22
+ }[];
@@ -0,0 +1,173 @@
1
+ // spec/findings.md §1b: prompt injection (PROMPT_INJECTION, squawk HIJACK). What reaches the model
2
+ // from outside the agent's instructions is scanned: each MCP tool result, and the newest turn of
3
+ // each model request (the tool results and the person's words it carries). Instruction overrides,
4
+ // role hijacks, exfiltration asks and hidden text, in English and Arabic. One finding per event and
5
+ // source; the finding names the rule, never the text. Mirrors analysis/injection.py; pinned by
6
+ // spec/vectors/injection.json.
7
+ const decoder = new TextDecoder();
8
+ const MAX_TEXT = 200_000; // characters scanned per event: an injection is near the start or not worth the cost
9
+ /** The rules, in the order they're tried. Patterns run on ASCII-lowercased, space-folded text. */
10
+ export const INJECTION_RULES = [
11
+ {
12
+ id: "override",
13
+ re: /\b(ignore|disregard|forget|override|bypass)( all| any| the| your| these| those| my)* (previous|prior|above|earlier|preceding|system|original|safety)( \w+)? (instructions|prompts?|rules|directions|guidelines|messages|policies)\b/,
14
+ },
15
+ { id: "override", re: /(تجاهل|انس|انسى|تخط) (جميع |كل )?(التعليمات|الأوامر|القواعد|ما سبق)/ },
16
+ {
17
+ id: "role",
18
+ re: /(\byou are now\b|\bfrom now on,? you are\b|\bact as\b) (dan|an? unrestricted|a jailbroken|in developer mode)\b|<\|im_start\|>|<\|system\|>|\[system\]|### (system|new instructions)|\bnew instructions:/,
19
+ },
20
+ {
21
+ id: "exfiltrate",
22
+ re: /\b(send|post|upload|forward|email|exfiltrate|leak|paste)\b.{0,40}\b(system prompt|api keys?|passwords?|credentials|secrets?|conversation history|chat history|access tokens?|private keys?)\b/,
23
+ },
24
+ {
25
+ id: "exfiltrate",
26
+ re: /\b(reveal|print|repeat|show|output) (me )?(your|the) (system prompt|hidden instructions|initial instructions|original instructions)\b/,
27
+ },
28
+ {
29
+ id: "exfiltrate",
30
+ re: /(أرسل|ارسل|أفصح عن|اكشف) .{0,40}(كلمة المرور|كلمات المرور|مفتاح|بيانات الدخول|التعليمات المخفية)/,
31
+ },
32
+ ];
33
+ /** Text no reader sees: Unicode tag characters, bidi overrides, a run of 4+ zero-width characters. */
34
+ export function hiddenText(text) {
35
+ let zw = 0;
36
+ for (const ch of text) {
37
+ const c = ch.codePointAt(0);
38
+ if (c >= 0xe0000 && c <= 0xe007f)
39
+ return true;
40
+ if ((c >= 0x202a && c <= 0x202e) || (c >= 0x2066 && c <= 0x2069))
41
+ return true;
42
+ if (c === 0x200b || c === 0x200c || c === 0x200d || c === 0x2060 || c === 0xfeff) {
43
+ zw += 1;
44
+ if (zw >= 4)
45
+ return true;
46
+ }
47
+ else
48
+ zw = 0;
49
+ }
50
+ return false;
51
+ }
52
+ /** ASCII lowercase; spaces, tabs, newlines and line separators to one space: the same in every language. */
53
+ const fold = (s) => s.replace(/[A-Z]/g, (c) => c.toLowerCase()).replace(/[ \t\r\n\u2028\u2029]+/g, " ");
54
+ /** The first rule a text breaks, or null. */
55
+ export function injectionRule(text) {
56
+ const t = Array.from(text).slice(0, MAX_TEXT).join("");
57
+ if (hiddenText(t))
58
+ return "hidden";
59
+ const f = fold(t);
60
+ for (const r of INJECTION_RULES)
61
+ if (r.re.test(f))
62
+ return r.id;
63
+ return null;
64
+ }
65
+ /** Every string in a JSON value, depth first, in key order. */
66
+ function strings(v, out) {
67
+ if (typeof v === "string")
68
+ out.push(v);
69
+ else if (Array.isArray(v))
70
+ for (const x of v)
71
+ strings(x, out);
72
+ else if (v && typeof v === "object")
73
+ for (const x of Object.values(v))
74
+ strings(x, out);
75
+ }
76
+ const textOf = (v) => {
77
+ const out = [];
78
+ strings(v, out);
79
+ return out.join("\n");
80
+ };
81
+ /** The newest turn of a model request: what it adds to the conversation. */
82
+ function newestTurn(body) {
83
+ const out = [];
84
+ const messages = body.messages;
85
+ if (Array.isArray(messages) && messages.length) {
86
+ // Chat Completions: the trailing tool messages, and the last message itself
87
+ let i = messages.length - 1;
88
+ while (i > 0 && messages[i].role === "tool")
89
+ i -= 1;
90
+ for (const m of messages.slice(i)) {
91
+ if (m.role === "tool")
92
+ out.push({ source: "tool_result", text: textOf(m.content) });
93
+ else if (m.role === "user") {
94
+ // Anthropic: a user turn holds the tool results and the person's words
95
+ const blocks = Array.isArray(m.content) ? m.content : [m.content];
96
+ const results = blocks.filter((b) => b?.type === "tool_result");
97
+ const words = blocks.filter((b) => b?.type !== "tool_result");
98
+ if (results.length)
99
+ out.push({ source: "tool_result", text: textOf(results) });
100
+ if (words.length)
101
+ out.push({ source: "user", text: textOf(words) });
102
+ }
103
+ }
104
+ return out;
105
+ }
106
+ const input = body.input;
107
+ if (typeof input === "string")
108
+ return [{ source: "user", text: input }];
109
+ if (Array.isArray(input) && input.length) {
110
+ // Responses: the trailing function_call_output items, then the last item
111
+ let i = input.length - 1;
112
+ while (i > 0 && input[i].type === "function_call_output")
113
+ i -= 1;
114
+ for (const item of input.slice(i)) {
115
+ if (item.type === "function_call_output")
116
+ out.push({ source: "tool_result", text: textOf(item.output) });
117
+ else if (item.role === "user")
118
+ out.push({ source: "user", text: textOf(item.content) });
119
+ }
120
+ }
121
+ return out;
122
+ }
123
+ /** spec/findings.md §1b: PROMPT_INJECTION findings over a record. */
124
+ export function injectionFindings(lines, bodies) {
125
+ const out = [];
126
+ const add = (seq, source, rule, tool) => out.push({
127
+ code: "PROMPT_INJECTION",
128
+ source: "detectors",
129
+ severity: "warning",
130
+ ref: { seq, source, rule, ...(tool !== undefined ? { tool } : {}) },
131
+ detail: rule === "hidden"
132
+ ? "Text the reader can't see (hidden characters) reached the model: a classic way to smuggle instructions."
133
+ : "Text that reached the model tries to give it new instructions: an override, a role change, or a request to send out secrets.",
134
+ });
135
+ for (const l of lines) {
136
+ const e = JSON.parse(l);
137
+ if (e.kind !== "tool.result" && e.kind !== "llm.request")
138
+ continue;
139
+ const raw = bodies(e.body_hash);
140
+ if (!raw || raw.length === 0)
141
+ continue;
142
+ let doc;
143
+ try {
144
+ doc = JSON.parse(decoder.decode(raw));
145
+ }
146
+ catch {
147
+ continue;
148
+ }
149
+ if (e.kind === "tool.result") {
150
+ const result = doc?.result;
151
+ if (result === undefined)
152
+ continue;
153
+ const rule = injectionRule(textOf(result));
154
+ const tool = typeof e.meta.tool === "string" ? e.meta.tool : undefined;
155
+ if (rule)
156
+ add(e.seq, "tool_result", rule, tool);
157
+ continue;
158
+ }
159
+ if (!doc || typeof doc !== "object" || Array.isArray(doc))
160
+ continue;
161
+ const seen = new Set();
162
+ for (const part of newestTurn(doc)) {
163
+ if (seen.has(part.source))
164
+ continue;
165
+ const rule = injectionRule(part.text);
166
+ if (rule) {
167
+ seen.add(part.source);
168
+ add(e.seq, part.source, rule);
169
+ }
170
+ }
171
+ }
172
+ return out;
173
+ }
@@ -18,6 +18,7 @@ export const MITRE_ATLAS = {
18
18
  "AML.T0034.000": "Cost Harvesting: Excessive Queries",
19
19
  "AML.T0034.001": "Cost Harvesting: Resource-Intensive Queries",
20
20
  "AML.T0034.002": "Cost Harvesting: Agentic Resource Consumption",
21
+ "AML.T0051": "LLM Prompt Injection",
21
22
  "AML.T0053": "AI Agent Tool Invocation",
22
23
  "AML.T0057": "LLM Data Leakage",
23
24
  "AML.T0080.000": "AI Agent Context Poisoning: Memory",
@@ -53,6 +54,9 @@ export const TAXONOMY = {
53
54
  STERILE_VIOLATION: { owasp: ["ASI02", "ASI10"], atlas: ["AML.T0053"] },
54
55
  TOKEN_MISUSE: { owasp: ["ASI03"], atlas: ["AML.T0091.000", "AML.T0086"] },
55
56
  TOOL_CHANGED: { owasp: ["ASI04"], atlas: ["AML.T0110.002"] },
57
+ TOOL_DRIFTED: { owasp: ["ASI04"], atlas: ["AML.T0110.002"] },
58
+ PROMPT_INJECTION: { owasp: ["ASI01"], atlas: ["AML.T0051"] },
59
+ SECRET_SENT: { owasp: ["ASI03"], atlas: ["AML.T0057"] },
56
60
  TOOL_OUTPUT_MISMATCH: { owasp: ["ASI04", "ASI06"], atlas: ["AML.T0110.002", "AML.T0099"] },
57
61
  TOOL_WITNESS_INVALID: { owasp: ["ASI04"], atlas: ["AML.T0110.002"] },
58
62
  };
package/dist/index.d.ts CHANGED
@@ -2,7 +2,8 @@ export { A2A_STREAMING, type AgentCardReport, a2aMethod, a2aRequestMeta, a2aResp
2
2
  export { causality, memoryXray } from "./agents/index.ts";
3
3
  export { type DetectorOptions, detectors, fingerprint } from "./analysis/detectors.ts";
4
4
  export { FAULTS, type Fault, faultOf } from "./analysis/faults.ts";
5
- export { type Accuracy, type AnalyzeOptions, type AnswerClaims, analyze, answerClaims, answerCredential, answerRisk, answersOf, answerText, type ClaimEntry, type ClaimVerdict, type CriterionResult, canonicalNumber, checkAgainstPacks, checkFlightPlan, checkpointReplay, detectorAccuracy, dualWitness, Evidence, eventsOf, type Fact, type Finding as AnalysisFinding, factsIn, findingKey, type GroundedClaim, groundingFindings, groundingRequest, HOLD_TRIGGERS, identifiersIn, judgeFindings, judgeRequest, type Label as AccuracyLabel, type Landing, landing, loadReferencePacks, modelGroundingFindings, normaliseValue, OUTCOMES, type Outcome, type OutcomeRollup, packsFor, parseGroundingAnswer, parseJudgeAnswer, type ReferenceFact, type ReferencePack, type ReferenceVerdict, referenceContext, rollup, type Severity, schemaProblem, toolHallucinations, verifyAnswerCredential, type WasteReason, type WasteReport, waste, wilsonLow, } from "./analysis/index.ts";
5
+ export { type Accuracy, type AnalyzeOptions, type AnswerClaims, analyze, answerClaims, answerCredential, answerRisk, answersOf, answerText, type ClaimEntry, type ClaimVerdict, type CriterionResult, canonicalNumber, checkAgainstPacks, checkFlightPlan, checkpointReplay, detectorAccuracy, dualWitness, Evidence, eventsOf, type Fact, type Finding as AnalysisFinding, factsIn, findingKey, type GroundedClaim, groundingFindings, groundingRequest, HOLD_TRIGGERS, identifiersIn, judgeFindings, judgeRequest, type Label as AccuracyLabel, type Landing, landing, loadReferencePacks, modelGroundingFindings, normaliseValue, OUTCOMES, type Outcome, type OutcomeRollup, packsFor, parseGroundingAnswer, parseJudgeAnswer, type ReferenceFact, type ReferencePack, type ReferenceVerdict, referenceContext, rollup, type Severity, schemaProblem, secretSent, toolDrifted, toolHallucinations, userDownvotes, verifyAnswerCredential, type WasteReason, type WasteReport, waste, wilsonLow, } from "./analysis/index.ts";
6
+ export { injectionFindings, injectionRule } from "./analysis/injection.ts";
6
7
  export { MITRE_ATLAS, OWASP_ASI, TAXONOMY, type Taxonomy, taxonomyOf, } from "./analysis/taxonomy.ts";
7
8
  export { type ApprovalFinding, approvalConfirmation, approvalFindings, argsHash, verifyConfirmation, } from "./approvals/index.ts";
8
9
  export { textWarnings } from "./approvals/warnings.ts";
package/dist/index.js CHANGED
@@ -4,7 +4,8 @@ export { A2A_STREAMING, a2aMethod, a2aRequestMeta, a2aResponseMeta, a2aRestMetho
4
4
  export { causality, memoryXray } from "./agents/index.js";
5
5
  export { detectors, fingerprint } from "./analysis/detectors.js";
6
6
  export { FAULTS, faultOf } from "./analysis/faults.js";
7
- export { analyze, answerClaims, answerCredential, answerRisk, answersOf, answerText, canonicalNumber, checkAgainstPacks, checkFlightPlan, checkpointReplay, detectorAccuracy, dualWitness, Evidence, eventsOf, factsIn, findingKey, groundingFindings, groundingRequest, HOLD_TRIGGERS, identifiersIn, judgeFindings, judgeRequest, landing, loadReferencePacks, modelGroundingFindings, normaliseValue, OUTCOMES, packsFor, parseGroundingAnswer, parseJudgeAnswer, referenceContext, rollup, schemaProblem, toolHallucinations, verifyAnswerCredential, waste, wilsonLow, } from "./analysis/index.js";
7
+ export { analyze, answerClaims, answerCredential, answerRisk, answersOf, answerText, canonicalNumber, checkAgainstPacks, checkFlightPlan, checkpointReplay, detectorAccuracy, dualWitness, Evidence, eventsOf, factsIn, findingKey, groundingFindings, groundingRequest, HOLD_TRIGGERS, identifiersIn, judgeFindings, judgeRequest, landing, loadReferencePacks, modelGroundingFindings, normaliseValue, OUTCOMES, packsFor, parseGroundingAnswer, parseJudgeAnswer, referenceContext, rollup, schemaProblem, secretSent, toolDrifted, toolHallucinations, userDownvotes, verifyAnswerCredential, waste, wilsonLow, } from "./analysis/index.js";
8
+ export { injectionFindings, injectionRule } from "./analysis/injection.js";
8
9
  export { MITRE_ATLAS, OWASP_ASI, TAXONOMY, taxonomyOf, } from "./analysis/taxonomy.js";
9
10
  export { approvalConfirmation, approvalFindings, argsHash, verifyConfirmation, } from "./approvals/index.js";
10
11
  export { textWarnings } from "./approvals/warnings.js";
@@ -6,6 +6,8 @@ export interface Block {
6
6
  id?: string;
7
7
  name?: string;
8
8
  input?: unknown;
9
+ /** Anthropic: where a text block's words come from (web search: the page, its title, the quote). */
10
+ citations?: unknown[];
9
11
  }
10
12
  export interface Call {
11
13
  requestSeq: number;
@@ -182,6 +182,10 @@ function rebuild(call, body) {
182
182
  block.thinking = (block.thinking ?? "") + d.thinking;
183
183
  else if (d.type === "input_json_delta" && typeof d.partial_json === "string")
184
184
  partial.set(event.index, (partial.get(event.index) ?? "") + d.partial_json);
185
+ else if (d.type === "citations_delta" && isObj(d.citation)) {
186
+ block.citations ??= [];
187
+ block.citations.push(d.citation);
188
+ }
185
189
  }
186
190
  else if (type === "content_block_stop" && typeof event.index === "number") {
187
191
  const block = call.blocks.get(event.index);
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.16.1";
1
+ export declare const VERSION = "0.18.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // The SDK version, on its own so the CLI can print it without loading the whole SDK.
2
- export const VERSION = "0.16.1";
2
+ export const VERSION = "0.18.0";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zanii/blackbox",
3
3
  "license": "Apache-2.0",
4
- "version": "0.16.1",
4
+ "version": "0.18.0",
5
5
  "description": "The flight recorder for AI agents: sessions, a zero-loss spool, framework hooks, approvals, and offline verification of the gateway's hash-chained record.",
6
6
  "keywords": [
7
7
  "ai-agents",