@ciphyrshq/sdk 2.6.0 → 3.0.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/types.d.ts CHANGED
@@ -83,9 +83,22 @@ export interface WaitForJobOptions {
83
83
  export interface ProtectOptions extends MaskOptions {
84
84
  /** Default true — purge vault after restore (data minimisation). */
85
85
  purge?: boolean;
86
+ /**
87
+ * What to do when the masker is unreachable. DEFAULT FALSE (fail closed):
88
+ * the call throws and your LLM never sees the raw text. `true` sends the
89
+ * RAW, UNMASKED input to your LLM and returns it unrestored with
90
+ * `failedOpen: true`. Falls back to the client-wide `failOpen`.
91
+ *
92
+ * `null` means unset, not "fail open". The legacy `failClosed` key is NOT
93
+ * read here — it has never done anything on scan.protect and still does
94
+ * not, because on this surface turning fail-closed off means sending
95
+ * unmasked PII to your model.
96
+ */
97
+ failOpen?: boolean | null;
86
98
  }
87
99
  export interface ProtectContext {
88
- sessionId: string;
100
+ /** null only on the failOpen path, where no vault session was ever created. */
101
+ sessionId: string | null;
89
102
  entitiesFound: any[];
90
103
  }
91
104
  export interface ProtectResult {
@@ -95,9 +108,15 @@ export interface ProtectResult {
95
108
  maskedInput: string;
96
109
  /** What the LLM returned, before unmask (audit). */
97
110
  maskedOutput: string;
98
- sessionId: string;
111
+ sessionId: string | null;
99
112
  entitiesFound: any[];
100
113
  tokensRestored: number;
114
+ /**
115
+ * Present only when `failOpen: true` and the masker was unreachable — the
116
+ * text was NOT masked. Absent on every normal round-trip, so auditing
117
+ * "was this really masked?" is a property check, not a guess.
118
+ */
119
+ failedOpen?: true;
101
120
  }
102
121
  export type ProtectLLMCall = (masked: string, ctx: ProtectContext) => Promise<string>;
103
122
 
@@ -260,6 +279,22 @@ export interface CiphyrsClientOptions {
260
279
  dashUrl?: string;
261
280
  /** Request timeout in milliseconds (default: 10000) */
262
281
  timeout?: number;
282
+ /**
283
+ * Fleet-wide failure posture for protectTool, guard.wrap and scan.protect
284
+ * when Ciphyrs cannot be reached.
285
+ *
286
+ * DEFAULT FALSE — FAIL CLOSED: the tool is not executed, the LLM is not
287
+ * called, and the error surfaces. This is a BEHAVIOUR CHANGE for
288
+ * protectTool, which previously failed OPEN and ran the tool unauthorised
289
+ * during an outage while the other two refused to proceed. Set `true` to
290
+ * restore the old behaviour across the client; `{ failOpen: true }` on an
291
+ * individual call overrides this either way. An existing protectTool caller
292
+ * who passed `failClosed: false` still fails open, unchanged — but that
293
+ * legacy key is read on protectTool ONLY, never here.
294
+ *
295
+ * `null` means unset (fail closed), not fail open.
296
+ */
297
+ failOpen?: boolean | null;
263
298
  }
264
299
 
265
300
  // ── V58 Guard ────────────────────────────────────────────────────────────
@@ -301,13 +336,32 @@ export interface GuardWrapResult {
301
336
  reason?: string;
302
337
  decision: GuardDecision;
303
338
  detections?: GuardCheckResult['detections'];
304
- decision_id: string;
339
+ decision_id: string | null;
340
+ /** Present only when a check was skipped because the guard was unreachable
341
+ * and `failOpen` was set — i.e. this "allow" was never actually decided. */
342
+ failedOpen?: true;
343
+ }
344
+
345
+ /** The one failure-posture switch, shared by guard.wrap, scan.protect and protectTool. */
346
+ export interface FailPostureOptions {
347
+ /**
348
+ * Run anyway when Ciphyrs is unreachable. DEFAULT FALSE — fail closed:
349
+ * the LLM is not called / the tool is not executed and the error surfaces.
350
+ * Falls back to the client-wide `failOpen` when omitted.
351
+ *
352
+ * `null` is accepted and means UNSET, exactly like omitting the key, so a
353
+ * posture read out of JSON config, a database column or a `{ ...defaults }`
354
+ * spread falls through to the client posture instead of choosing one.
355
+ * Anything the SDK cannot read as an unmistakable yes or no is unset too —
356
+ * the fail-open direction is only ever reached by a value that says so.
357
+ */
358
+ failOpen?: boolean | null;
305
359
  }
306
360
  export type GuardLLMCall = (input: string) => Promise<string>;
307
361
 
308
362
  export declare class GuardResource {
309
363
  check(params: GuardCheckParams): Promise<GuardCheckResult>;
310
- wrap(userInput: string, llmCall: GuardLLMCall, opts?: GuardCheckParams): Promise<GuardWrapResult>;
364
+ wrap(userInput: string, llmCall: GuardLLMCall, opts?: GuardCheckParams & FailPostureOptions): Promise<GuardWrapResult>;
311
365
  getPolicy(): Promise<{ policy_mode: GuardPolicyMode; modes: any[] }>;
312
366
  setPolicy(mode: GuardPolicyMode): Promise<{ policy_mode: GuardPolicyMode }>;
313
367
  decisions(opts?: { limit?: number; decision?: GuardDecision }): Promise<{ decisions: any[]; total: number }>;
@@ -379,4 +433,210 @@ export declare class CiphyrsClient {
379
433
  maskAsync(text: string, opts?: MaskAsyncOptions): Promise<MaskAsyncResult>;
380
434
  waitForJob(jobId: string, opts?: WaitForJobOptions): Promise<JobResult>;
381
435
  createKey(opts?: { name?: string; scopes?: string[] }): Promise<{ apiKey: string; key: ApiKey }>;
382
- }
436
+
437
+ // ── Agent health (V39 heartbeat, V143 windows + metrics) ────────────────
438
+ reportHeartbeat(args: {
439
+ agentName?: string; agentId?: string;
440
+ status?: AgentHealthStatus; projectName?: string;
441
+ latencyMs?: number | null; errorMessage?: string | null;
442
+ environment?: string; heartbeatIntervalS?: number;
443
+ metrics?: AgentProcessMetrics; metadata?: Record<string, unknown>;
444
+ }): Promise<{ ok: boolean; agent_id?: string; prev_status?: string; new_status?: string; error?: string }>;
445
+ startHealthMonitor(args: {
446
+ agentName?: string; agentId?: string; projectName?: string;
447
+ intervalMs?: number; environment?: string;
448
+ probe?: () => Promise<{ status?: AgentHealthStatus; latencyMs?: number } | void>;
449
+ }): ReturnType<typeof setInterval>;
450
+ stopHealthMonitor(): void;
451
+ setAgentMonitoring(agentId: string, opts: {
452
+ heartbeatTimeoutS?: number | null; probeUrl?: string | null;
453
+ }): Promise<{ monitoring: { id: string; name: string; heartbeat_interval_s: number | null; heartbeat_timeout_s: number | null; probe_url: string | null; probe_status: string | null } }>;
454
+ }
455
+
456
+ // ═════════════════════════════════════════════════════════════════════════
457
+ // Tracing (2.7) — spans, and the cross-process propagation the topology
458
+ // graph is derived from. See src/propagation.js.
459
+ // ═════════════════════════════════════════════════════════════════════════
460
+
461
+ export type AgentHealthStatus = 'up' | 'degraded' | 'failing' | 'down' | 'unknown';
462
+
463
+ /** Process metrics shipped with a heartbeat; the server turns a struggling
464
+ * process into `degraded` rather than waiting for it to go silent. */
465
+ export interface AgentProcessMetrics {
466
+ rss_mb?: number; heap_mb?: number; cpu_pct?: number;
467
+ event_loop_lag_ms?: number; thread_lag_ms?: number;
468
+ error_rate?: number; uptime_s?: number;
469
+ spans_since_last?: number; errors_since_last?: number; active_traces?: number;
470
+ runtime?: string; sdk_version?: string; hostname?: string;
471
+ }
472
+
473
+ export type SpanKind = 'agent' | 'tool' | 'llm_call' | 'chain' | 'retrieval' | 'communication' | 'handoff';
474
+
475
+ export interface SpanOptions {
476
+ /** Explicit parent. Omit and the enclosing span is used automatically. */
477
+ parentSpanId?: string | null;
478
+ agentName?: string;
479
+ kind?: SpanKind;
480
+ /** Opt out of auto-entering ambient context; manage it with span.run(). */
481
+ scoped?: boolean;
482
+ }
483
+
484
+ export declare class Span {
485
+ readonly spanId: string;
486
+ readonly agentName?: string;
487
+ setInput(text: string): this;
488
+ setOutput(text: string): this;
489
+ setStatus(status: 'ok' | 'error'): this;
490
+ setError(message: string): this;
491
+ setCost(usd: number): this;
492
+ setModel(name: string): this;
493
+ setTokens(prompt: number, completion: number): this;
494
+ setMetadata(meta: Record<string, unknown>): this;
495
+ /** Run `fn` with this span active, then end it (even if `fn` throws). */
496
+ run<T>(fn: (span: Span) => Promise<T> | T): Promise<T>;
497
+ /** Make this span ambient for the rest of the async context; end() unwinds. */
498
+ enter(): this;
499
+ end(): this;
500
+ }
501
+
502
+ export declare class Trace {
503
+ readonly traceId: string;
504
+ readonly name: string;
505
+ /** True when this trace continues one started by another agent/process. */
506
+ readonly isContinuation: boolean;
507
+ /** The agent that called us, when the inbound request named one. */
508
+ readonly callerAgent: string | null;
509
+ span(name: string, opts?: SpanOptions): Span;
510
+ run<T>(fn: (trace: Trace) => Promise<T> | T): Promise<T>;
511
+ }
512
+
513
+ export interface TracerOptions {
514
+ projectName: string;
515
+ flushIntervalMs?: number;
516
+ batchSize?: number;
517
+ /** The agent this process runs — visible in the fleet before any traffic. */
518
+ agentName?: string;
519
+ /** Liveness beat for every agent seen. 0 disables. Default 60000. */
520
+ heartbeatIntervalMs?: number;
521
+ heartbeatMetrics?: boolean;
522
+ /** Inject traceparent/baggage into outbound calls. Default true. */
523
+ propagate?: boolean;
524
+ }
525
+
526
+ export declare class CiphyrsTracer {
527
+ constructor(client: CiphyrsClient, opts: TracerOptions);
528
+ readonly knownAgents: string[];
529
+ /** Start a trace — or continue the caller's, when serving an instrumented request. */
530
+ trace(name: string, opts?: { traceId?: string; headers?: unknown }): Trace;
531
+ withTrace<T>(name: string, fn: (trace: Trace) => Promise<T> | T, opts?: { traceId?: string; headers?: unknown }): Promise<T>;
532
+ emitMetric(name: string, value: number, opts?: { unit?: string; tags?: Record<string, string>; agentName?: string }): void;
533
+ collectMetrics(): AgentProcessMetrics;
534
+ flush(): Promise<void>;
535
+ shutdown(): Promise<void>;
536
+ }
537
+
538
+ /** What an inbound request told us about the trace it belongs to. */
539
+ export interface RemoteTraceContext {
540
+ trace_id: string;
541
+ span_id?: string;
542
+ agent_name?: string;
543
+ project?: string;
544
+ sampled: boolean;
545
+ }
546
+
547
+ export declare const TRACEPARENT: 'traceparent';
548
+ export declare const BAGGAGE: 'baggage';
549
+
550
+ /** Add traceparent/baggage for the active span. No-op outside a trace. */
551
+ export declare function inject<H extends object>(headers?: H): H;
552
+ /** Read the caller's context from inbound headers (object, Headers, entry array or CGI env). */
553
+ export declare function extract(headers: unknown): RemoteTraceContext | undefined;
554
+ /** Run `fn` as a continuation of the caller's trace. */
555
+ export declare function withRemoteContext<T>(headersOrCtx: unknown, fn: () => T): T;
556
+ export declare function currentContext(): RemoteTraceContext | undefined;
557
+ export declare function instrumentFetch(): boolean;
558
+ export declare function instrumentHttp(): boolean;
559
+ export declare function autoInstrument(): { fetch: boolean; http: boolean };
560
+ export declare function expressMiddleware(): (req: any, res: any, next: () => void) => unknown;
561
+ export declare function fastifyPlugin(app: any): Promise<void>;
562
+ export declare function withPropagation<H extends (req: any, res: any) => any>(handler: H): H;
563
+ export declare function w3cTraceId(traceId: string): string;
564
+ export declare function w3cSpanId(spanId: string): string;
565
+ /** The active trace/span ids — what a guard call attributes itself to. */
566
+ export declare function activeIds(): { trace_id?: string; span_id?: string };
567
+ /**
568
+ * The kind of the innermost LOCAL span, or undefined when none is open.
569
+ * A remote parent has no kind (it does not travel in baggage) and a trace is
570
+ * not a span, so both answer undefined. protectTool uses this to tell whether
571
+ * the span it is inside is the tool span it may name to the gateway.
572
+ */
573
+ export declare function activeSpanKind(): 'agent' | 'tool' | 'llm' | 'retriever' | string | undefined;
574
+ export declare function activeAgent(): string | undefined;
575
+ export declare function remoteParent(): RemoteTraceContext | undefined;
576
+
577
+ // ── V60 Tool-call authorization ──────────────────────────────────────────
578
+
579
+ export declare class ToolBlocked extends Error {
580
+ action: string;
581
+ reason: string;
582
+ ruleId?: string;
583
+ ruleName?: string;
584
+ decisionId?: string;
585
+ constructor(action: string, reason: string, opts?: { ruleId?: string; ruleName?: string; decisionId?: string });
586
+ }
587
+ export declare class ToolApprovalTimeout extends ToolBlocked {}
588
+
589
+ export interface ProtectToolOptions extends FailPostureOptions {
590
+ /** Agent name rules are scoped to. Required. */
591
+ agent: string;
592
+ /** Tool name reported to Ciphyrs (default: the function's name). */
593
+ name?: string;
594
+ /** Shown in the tool inventory / allowlist UI. */
595
+ description?: string;
596
+ /** Approval poll interval in ms (default 1000). */
597
+ pollIntervalMs?: number;
598
+ /** Hard cap on approval polling; defaults to whatever the rule says. */
599
+ maxPollSeconds?: number;
600
+ /**
601
+ * Legacy spelling of `failOpen`, inverted, honoured in BOTH directions:
602
+ * `failClosed: false` still means fail open. Prefer `failOpen`.
603
+ *
604
+ * protectTool is the ONLY surface that reads this key — it is the only one
605
+ * it ever shipped on. On `guard.wrap`, `scan.protect` and the client
606
+ * constructor it is inert, as it has always been; use `failOpen` there.
607
+ *
608
+ * `null` means unset, i.e. the same as omitting it. It used to mean fail
609
+ * OPEN, because `!null` is true — so a posture nobody had set ran the tool
610
+ * during an outage.
611
+ * @deprecated
612
+ */
613
+ failClosed?: boolean | null;
614
+ }
615
+
616
+ /**
617
+ * Gate an async tool behind Ciphyrs tool-call authorization.
618
+ *
619
+ * FAILS CLOSED by default: if Ciphyrs cannot be reached the tool is NOT run
620
+ * and ToolBlocked is thrown. This changed — protectTool used to fail open —
621
+ * because this is the path that authorises tool execution. Opt back out with
622
+ * `{ failOpen: true }` here or `new CiphyrsClient({ failOpen: true })`.
623
+ *
624
+ * Sends the active `trace_id` on every gate call. The `span_id` is sent ONLY
625
+ * when the enclosing span is a TOOL span (`span(name, { kind: 'tool' })`) —
626
+ * i.e. when the id names the span this tool call will actually export.
627
+ * protectTool emits no span of its own, so inside an agent span, an llm span,
628
+ * a propagated remote context, or no span at all, the span id is omitted.
629
+ *
630
+ * That is deliberate and it is not merely caution: the gateway matches a tool
631
+ * span to its gate call by span id, and reads ANY producer-supplied id as
632
+ * proof that this agent's spans CAN be matched exactly. Sending the id of a
633
+ * span that is not the tool span leaves the exact match impossible while
634
+ * moving the agent from "not correlated, do not judge" into judgement on an
635
+ * unvalidated name/time fallback. Open a tool-kind span around the call if you
636
+ * want exact correlation.
637
+ */
638
+ export declare function protectTool<F extends (...args: any[]) => Promise<any>>(
639
+ client: CiphyrsClient,
640
+ opts: ProtectToolOptions,
641
+ fn: F,
642
+ ): F;