@cadreen/sdk 0.1.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.
@@ -0,0 +1,33 @@
1
+ import type { IntelligenceTraceEntry, ListIntelligenceResponse, TraceExplain } from "../types";
2
+ import { TracesResource } from "./traces";
3
+ /**
4
+ * FailuresResource — Builder-shaped naming facade for understanding errors.
5
+ *
6
+ * When something goes wrong, developers ask `cadreen.failures.explain(id)`
7
+ * or `cadreen.failures.recent()` instead of navigating the trace API directly.
8
+ *
9
+ * This is a thin wrapper; no new backend routes are introduced.
10
+ */
11
+ export declare class FailuresResource {
12
+ private traces;
13
+ constructor(traces: TracesResource);
14
+ /** Fetch a trace and return it with an `.explain()` helper. */
15
+ explain(id: string): Promise<IntelligenceTraceEntry & {
16
+ explain: () => TraceExplain;
17
+ }>;
18
+ /** List recent traces with optional filtering. */
19
+ recent(options?: {
20
+ domain?: string;
21
+ decision?: string;
22
+ from?: string;
23
+ to?: string;
24
+ limit?: number;
25
+ offset?: number;
26
+ }): Promise<ListIntelligenceResponse>;
27
+ /** Summarize why an error occurred, using intelligence data when available. */
28
+ why(error: unknown): {
29
+ summary: string;
30
+ traceId?: string;
31
+ recommendation?: string;
32
+ };
33
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * FailuresResource — Builder-shaped naming facade for understanding errors.
3
+ *
4
+ * When something goes wrong, developers ask `cadreen.failures.explain(id)`
5
+ * or `cadreen.failures.recent()` instead of navigating the trace API directly.
6
+ *
7
+ * This is a thin wrapper; no new backend routes are introduced.
8
+ */
9
+ export class FailuresResource {
10
+ constructor(traces) {
11
+ this.traces = traces;
12
+ }
13
+ /** Fetch a trace and return it with an `.explain()` helper. */
14
+ async explain(id) {
15
+ return this.traces.get(id);
16
+ }
17
+ /** List recent traces with optional filtering. */
18
+ async recent(options) {
19
+ return this.traces.list(options);
20
+ }
21
+ /** Summarize why an error occurred, using intelligence data when available. */
22
+ why(error) {
23
+ if (error && typeof error === "object") {
24
+ const e = error;
25
+ const traceId = typeof e.traceId === "string"
26
+ ? e.traceId
27
+ : typeof e.trace_id === "string"
28
+ ? e.trace_id
29
+ : undefined;
30
+ const code = typeof e.code === "string" ? e.code : "unknown";
31
+ const message = typeof e.message === "string" ? e.message : String(error);
32
+ return {
33
+ summary: `${code}: ${message}`,
34
+ traceId,
35
+ recommendation: code === "timeout"
36
+ ? "Retry with a longer timeout or check network connectivity."
37
+ : code === "rate_limited"
38
+ ? "Back off and retry with exponential delay."
39
+ : code === "policy_blocked"
40
+ ? "Review the policy that blocked this request or request an override."
41
+ : traceId
42
+ ? `Look up trace ${traceId} for full provenance.`
43
+ : "Check recent traces for context.",
44
+ };
45
+ }
46
+ return {
47
+ summary: String(error),
48
+ recommendation: "Check recent traces for context.",
49
+ };
50
+ }
51
+ }
@@ -0,0 +1,12 @@
1
+ import type { CreatePolicyRequest, CreatePolicyResponse, ConfirmPolicyResponse, EvaluatePolicyRequest, EvaluatePolicyResponse, ListPoliciesResponse, PolicyBundle } from "../types";
2
+ import { PoliciesResource } from "./policies";
3
+ export declare class GuardrailsResource {
4
+ private policies;
5
+ constructor(policies: PoliciesResource);
6
+ check(request: EvaluatePolicyRequest): Promise<EvaluatePolicyResponse>;
7
+ add(request: CreatePolicyRequest): Promise<CreatePolicyResponse>;
8
+ requireApproval(description: string): Promise<CreatePolicyResponse>;
9
+ approve(id: string): Promise<ConfirmPolicyResponse>;
10
+ list(): Promise<ListPoliciesResponse>;
11
+ get(id: string): Promise<PolicyBundle>;
12
+ }
@@ -0,0 +1,23 @@
1
+ export class GuardrailsResource {
2
+ constructor(policies) {
3
+ this.policies = policies;
4
+ }
5
+ async check(request) {
6
+ return this.policies.evaluate(request);
7
+ }
8
+ async add(request) {
9
+ return this.policies.create(request);
10
+ }
11
+ async requireApproval(description) {
12
+ return this.policies.requireApproval(description);
13
+ }
14
+ async approve(id) {
15
+ return this.policies.confirm(id);
16
+ }
17
+ async list() {
18
+ return this.policies.list();
19
+ }
20
+ async get(id) {
21
+ return this.policies.get(id);
22
+ }
23
+ }
@@ -0,0 +1,7 @@
1
+ import type { IntentRequest, IntentResult } from "../types";
2
+ import { HttpClient } from "../client";
3
+ export declare class IntentResource {
4
+ private client;
5
+ constructor(client: HttpClient);
6
+ invoke(request: IntentRequest): Promise<IntentResult>;
7
+ }
@@ -0,0 +1,77 @@
1
+ function mapIntentResponse(raw) {
2
+ const intelligence = raw.intelligence || {
3
+ capability: { total_available: 0, healthy_count: 0 },
4
+ reasoning: {},
5
+ memory: { healthy: true },
6
+ governance: { active: false },
7
+ humility: {},
8
+ process: { started_at: "", steps_taken: 0, duration_ms: 0 },
9
+ field_stability: { stable: [], evolving: [], internal: [] },
10
+ };
11
+ const traceId = raw.trace_id || raw.id;
12
+ switch (raw.type) {
13
+ case "direct":
14
+ return {
15
+ type: "direct",
16
+ message: raw.message || { role: "assistant", content: "" },
17
+ intelligence,
18
+ traceId,
19
+ };
20
+ case "clarify":
21
+ return {
22
+ type: "clarify",
23
+ questions: (raw.clarification?.questions || []).map((q) => typeof q === "string" ? { id: "", question: q, type: "open", required: false } : q),
24
+ conversationId: raw.clarification?.conversation_id || "",
25
+ intelligence,
26
+ traceId,
27
+ };
28
+ case "mission":
29
+ return {
30
+ type: "execution",
31
+ execution: {
32
+ id: raw.mission?.id || "",
33
+ status: raw.mission?.status || "",
34
+ stream_url: raw.mission?.stream_url,
35
+ poll_url: raw.mission?.poll_url,
36
+ },
37
+ intelligence,
38
+ traceId,
39
+ };
40
+ case "blocked":
41
+ return {
42
+ type: "blocked",
43
+ policy: {
44
+ name: raw.meta?.governance?.decision || "policy",
45
+ reason: raw.meta?.governance?.reason || "blocked by policy",
46
+ },
47
+ intelligence,
48
+ traceId,
49
+ };
50
+ case "connect_required":
51
+ return {
52
+ type: "connect_required",
53
+ connection: {
54
+ endpoint: raw.mission?.stream_url || "",
55
+ reason: raw.meta?.governance?.reason || "connection required",
56
+ },
57
+ intelligence,
58
+ traceId,
59
+ };
60
+ default:
61
+ return {
62
+ type: "direct",
63
+ message: raw.message || { role: "assistant", content: "" },
64
+ intelligence,
65
+ traceId,
66
+ };
67
+ }
68
+ }
69
+ export class IntentResource {
70
+ constructor(client) {
71
+ this.client = client;
72
+ }
73
+ async invoke(request) {
74
+ const raw = await this.client.post("/api/v1/cadreen/intent", request);
75
+ return mapIntentResponse(raw);
76
+ }
77
+ }
@@ -0,0 +1,9 @@
1
+ import type { RememberRequest, CreateMemoryResponse, SearchMemoryRequest, SearchMemoryResponse, Atom } from "../types";
2
+ import { HttpClient } from "../client";
3
+ export declare class MemoryResource {
4
+ private client;
5
+ constructor(client: HttpClient);
6
+ remember(request: RememberRequest): Promise<CreateMemoryResponse>;
7
+ search(request: SearchMemoryRequest): Promise<SearchMemoryResponse>;
8
+ get(id: string): Promise<Atom>;
9
+ }
@@ -0,0 +1,30 @@
1
+ export class MemoryResource {
2
+ constructor(client) {
3
+ this.client = client;
4
+ }
5
+ async remember(request) {
6
+ return this.client.post("/api/v1/cadreen/memory", {
7
+ type: request.type,
8
+ content: request.content,
9
+ domain: request.domain,
10
+ scope: request.scope,
11
+ authority: request.authority,
12
+ tags: request.tags,
13
+ });
14
+ }
15
+ async search(request) {
16
+ const params = {
17
+ query: request.query,
18
+ };
19
+ if (request.domain)
20
+ params.domain = request.domain;
21
+ if (request.tag)
22
+ params.tag = request.tag;
23
+ if (request.limit)
24
+ params.limit = request.limit;
25
+ return this.client.get("/api/v1/cadreen/memory/search", params);
26
+ }
27
+ async get(id) {
28
+ return this.client.get(`/api/v1/cadreen/memory/${encodeURIComponent(id)}`);
29
+ }
30
+ }
@@ -0,0 +1,12 @@
1
+ import type { CreatePolicyRequest, CreatePolicyResponse, ConfirmPolicyResponse, EvaluatePolicyRequest, EvaluatePolicyResponse, ListPoliciesResponse, PolicyBundle } from "../types";
2
+ import { HttpClient } from "../client";
3
+ export declare class PoliciesResource {
4
+ private client;
5
+ constructor(client: HttpClient);
6
+ create(request: CreatePolicyRequest): Promise<CreatePolicyResponse>;
7
+ evaluate(request: EvaluatePolicyRequest): Promise<EvaluatePolicyResponse>;
8
+ confirm(id: string): Promise<ConfirmPolicyResponse>;
9
+ list(): Promise<ListPoliciesResponse>;
10
+ get(id: string): Promise<PolicyBundle>;
11
+ requireApproval(description: string): Promise<CreatePolicyResponse>;
12
+ }
@@ -0,0 +1,26 @@
1
+ export class PoliciesResource {
2
+ constructor(client) {
3
+ this.client = client;
4
+ }
5
+ async create(request) {
6
+ return this.client.post("/api/v1/cadreen/policies", request);
7
+ }
8
+ async evaluate(request) {
9
+ return this.client.post("/api/v1/cadreen/policies/evaluate", request);
10
+ }
11
+ async confirm(id) {
12
+ return this.client.post(`/api/v1/cadreen/policies/${encodeURIComponent(id)}/confirm`);
13
+ }
14
+ async list() {
15
+ return this.client.get("/api/v1/cadreen/policies");
16
+ }
17
+ async get(id) {
18
+ return this.client.get(`/api/v1/cadreen/policies/${encodeURIComponent(id)}`);
19
+ }
20
+ async requireApproval(description) {
21
+ return this.create({
22
+ name: description,
23
+ auto_draft: true,
24
+ });
25
+ }
26
+ }
@@ -0,0 +1,41 @@
1
+ import type { IntentResult, IntentContext, RememberRequest, CreateMemoryResponse, SearchMemoryRequest, SearchMemoryResponse, RegisterOpenAPIRequest, RegisterOpenAPIResponse, RegisterMCPRequest, RegisterMCPResponse, InstallComposioRequest } from "../types";
2
+ import { IntentResource } from "./intent";
3
+ import { MemoryResource } from "./memory";
4
+ import { ConnectionsResource } from "./connections";
5
+ /**
6
+ * SkillsResource — Builder-shaped naming facade for the system's capabilities.
7
+ *
8
+ * Instead of remembering that `intent` is the chat endpoint and `memory`
9
+ * is the knowledge store, developers work with `cadreen.skills.ask()`,
10
+ * `cadreen.skills.remember()`, and `cadreen.skills.connect()`.
11
+ *
12
+ * This is a thin wrapper; no new backend routes are introduced.
13
+ */
14
+ export declare class SkillsResource {
15
+ private intent;
16
+ private memory;
17
+ private connections;
18
+ constructor(intent: IntentResource, memory: MemoryResource, connections: ConnectionsResource);
19
+ /** Ask a question and get a direct answer or clarifying questions. */
20
+ ask(prompt: string, options?: {
21
+ conversation_id?: string;
22
+ context?: IntentContext;
23
+ stream?: boolean;
24
+ }): Promise<IntentResult>;
25
+ /** Execute an action (mission-bound intent). */
26
+ act(prompt: string, options?: {
27
+ conversation_id?: string;
28
+ context?: IntentContext;
29
+ stream?: boolean;
30
+ }): Promise<IntentResult>;
31
+ /** Store a memory atom. */
32
+ remember(request: RememberRequest): Promise<CreateMemoryResponse>;
33
+ /** Search stored memories. */
34
+ recall(request: SearchMemoryRequest): Promise<SearchMemoryResponse>;
35
+ /** Register an OpenAPI connector. */
36
+ connectOpenAPI(request: RegisterOpenAPIRequest): Promise<RegisterOpenAPIResponse>;
37
+ /** Register an MCP connector. */
38
+ connectMCP(request: RegisterMCPRequest): Promise<RegisterMCPResponse>;
39
+ /** Install a Composio integration. */
40
+ connectComposio(request: InstallComposioRequest): Promise<Record<string, unknown>>;
41
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * SkillsResource — Builder-shaped naming facade for the system's capabilities.
3
+ *
4
+ * Instead of remembering that `intent` is the chat endpoint and `memory`
5
+ * is the knowledge store, developers work with `cadreen.skills.ask()`,
6
+ * `cadreen.skills.remember()`, and `cadreen.skills.connect()`.
7
+ *
8
+ * This is a thin wrapper; no new backend routes are introduced.
9
+ */
10
+ export class SkillsResource {
11
+ constructor(intent, memory, connections) {
12
+ this.intent = intent;
13
+ this.memory = memory;
14
+ this.connections = connections;
15
+ }
16
+ /** Ask a question and get a direct answer or clarifying questions. */
17
+ async ask(prompt, options) {
18
+ return this.intent.invoke({
19
+ messages: [{ role: "user", content: prompt }],
20
+ mode: "chat",
21
+ conversation_id: options?.conversation_id,
22
+ context: options?.context,
23
+ stream: options?.stream,
24
+ });
25
+ }
26
+ /** Execute an action (mission-bound intent). */
27
+ async act(prompt, options) {
28
+ return this.intent.invoke({
29
+ messages: [{ role: "user", content: prompt }],
30
+ mode: "execution",
31
+ conversation_id: options?.conversation_id,
32
+ context: options?.context,
33
+ stream: options?.stream,
34
+ });
35
+ }
36
+ /** Store a memory atom. */
37
+ async remember(request) {
38
+ return this.memory.remember(request);
39
+ }
40
+ /** Search stored memories. */
41
+ async recall(request) {
42
+ return this.memory.search(request);
43
+ }
44
+ /** Register an OpenAPI connector. */
45
+ async connectOpenAPI(request) {
46
+ return this.connections.registerOpenAPI(request);
47
+ }
48
+ /** Register an MCP connector. */
49
+ async connectMCP(request) {
50
+ return this.connections.registerMCP(request);
51
+ }
52
+ /** Install a Composio integration. */
53
+ async connectComposio(request) {
54
+ return this.connections.installComposio(request);
55
+ }
56
+ }
@@ -0,0 +1,18 @@
1
+ import type { IntelligenceTraceEntry, ListIntelligenceResponse, IntelligenceStats, TraceExplain } from "../types";
2
+ import { HttpClient } from "../client";
3
+ export declare class TracesResource {
4
+ private client;
5
+ constructor(client: HttpClient);
6
+ get(id: string): Promise<IntelligenceTraceEntry & {
7
+ explain: () => TraceExplain;
8
+ }>;
9
+ list(options?: {
10
+ domain?: string;
11
+ decision?: string;
12
+ from?: string;
13
+ to?: string;
14
+ limit?: number;
15
+ offset?: number;
16
+ }): Promise<ListIntelligenceResponse>;
17
+ stats(): Promise<IntelligenceStats>;
18
+ }
@@ -0,0 +1,58 @@
1
+ export class TracesResource {
2
+ constructor(client) {
3
+ this.client = client;
4
+ }
5
+ async get(id) {
6
+ const trace = await this.client.get(`/api/v1/cadreen/intelligence/${encodeURIComponent(id)}`);
7
+ return {
8
+ ...trace,
9
+ explain: () => {
10
+ const meta = trace.meta;
11
+ const steps = [];
12
+ if (meta.capability.total_available > 0) {
13
+ steps.push(`${meta.capability.healthy_count}/${meta.capability.total_available} capabilities healthy`);
14
+ }
15
+ if (meta.governance.active) {
16
+ steps.push(`Governance: ${meta.governance.decision || "active"} (confidence: ${meta.governance.confidence || 0})`);
17
+ }
18
+ if (meta.humility.gaps_detected && meta.humility.gaps_detected > 0) {
19
+ steps.push(`${meta.humility.gaps_detected} gaps detected (${meta.humility.blocking || 0} blocking)`);
20
+ }
21
+ if (meta.memory.knowledge_queried) {
22
+ steps.push(`${meta.memory.knowledge_queried} knowledge items queried`);
23
+ }
24
+ const recommendations = [];
25
+ if (meta.humility.blocking && meta.humility.blocking > 0) {
26
+ recommendations.push("Resolve blocking capability gaps before proceeding");
27
+ }
28
+ if (!meta.memory.healthy) {
29
+ recommendations.push("Knowledge store is degraded — expect reduced context quality");
30
+ }
31
+ return {
32
+ summary: meta.summary || `Trace ${trace.id}: ${steps.join("; ")}`,
33
+ steps,
34
+ recommendations: recommendations.length > 0 ? recommendations : undefined,
35
+ };
36
+ },
37
+ };
38
+ }
39
+ async list(options) {
40
+ const params = {};
41
+ if (options?.domain)
42
+ params.domain = options.domain;
43
+ if (options?.decision)
44
+ params.decision = options.decision;
45
+ if (options?.from)
46
+ params.from = options.from;
47
+ if (options?.to)
48
+ params.to = options.to;
49
+ if (options?.limit)
50
+ params.limit = options.limit;
51
+ if (options?.offset)
52
+ params.offset = options.offset;
53
+ return this.client.get("/api/v1/cadreen/intelligence", params);
54
+ }
55
+ async stats() {
56
+ return this.client.get("/api/v1/cadreen/intelligence/stats");
57
+ }
58
+ }
@@ -0,0 +1,56 @@
1
+ import type { CadreenError } from "./client";
2
+ export interface SpanContext {
3
+ traceId: string;
4
+ spanId: string;
5
+ }
6
+ export interface TelemetrySpan {
7
+ setName(name: string): void;
8
+ setAttribute(key: string, value: string | number | boolean): void;
9
+ setStatus(status: "ok" | "error"): void;
10
+ end(): void;
11
+ }
12
+ export interface TelemetryMeter {
13
+ recordRequest(method: string, path: string, status: number, durationMs: number): void;
14
+ recordRetry(method: string, path: string, attempt: number): void;
15
+ recordStreamEvent(eventType: string): void;
16
+ }
17
+ export interface TelemetryProvider {
18
+ startSpan(name: string, options?: SpanOptions): TelemetrySpan;
19
+ getMeter(): TelemetryMeter;
20
+ }
21
+ export interface SpanOptions {
22
+ parent?: SpanContext;
23
+ attributes?: Record<string, string | number | boolean>;
24
+ }
25
+ export declare class NoOpSpan implements TelemetrySpan {
26
+ setName(): void;
27
+ setAttribute(): void;
28
+ setStatus(): void;
29
+ end(): void;
30
+ }
31
+ export declare class NoOpMeter implements TelemetryMeter {
32
+ recordRequest(): void;
33
+ recordRetry(): void;
34
+ recordStreamEvent(): void;
35
+ }
36
+ export declare class NoOpProvider implements TelemetryProvider {
37
+ startSpan(_name: string, _options?: SpanOptions): TelemetrySpan;
38
+ getMeter(): TelemetryMeter;
39
+ }
40
+ export declare class OpenTelemetryAdapter implements TelemetryProvider {
41
+ private tracer;
42
+ private requestCounter;
43
+ private retryCounter;
44
+ private streamCounter;
45
+ private durationHistogram;
46
+ constructor(tracer: any, meter: any);
47
+ startSpan(name: string, options?: SpanOptions): TelemetrySpan;
48
+ getMeter(): TelemetryMeter;
49
+ }
50
+ export declare function wrapWithTelemetry(provider: TelemetryProvider): {
51
+ onRequestStart(method: string, path: string): TelemetrySpan;
52
+ onRequestEnd(span: TelemetrySpan, method: string, path: string, status: number, durationMs: number): void;
53
+ onRetry(method: string, path: string, attempt: number): void;
54
+ onStreamEvent(eventType: string): void;
55
+ onError(span: TelemetrySpan, error: CadreenError): void;
56
+ };
@@ -0,0 +1,125 @@
1
+ export class NoOpSpan {
2
+ setName() { }
3
+ setAttribute() { }
4
+ setStatus() { }
5
+ end() { }
6
+ }
7
+ export class NoOpMeter {
8
+ recordRequest() { }
9
+ recordRetry() { }
10
+ recordStreamEvent() { }
11
+ }
12
+ export class NoOpProvider {
13
+ startSpan(_name, _options) {
14
+ return new NoOpSpan();
15
+ }
16
+ getMeter() {
17
+ return new NoOpMeter();
18
+ }
19
+ }
20
+ export class OpenTelemetryAdapter {
21
+ constructor(tracer, meter) {
22
+ this.tracer = tracer;
23
+ this.requestCounter = meter.createCounter("cadreen.client.requests", {
24
+ description: "Number of API requests made",
25
+ unit: "1",
26
+ });
27
+ this.retryCounter = meter.createCounter("cadreen.client.retries", {
28
+ description: "Number of request retries",
29
+ unit: "1",
30
+ });
31
+ this.streamCounter = meter.createCounter("cadreen.client.stream_events", {
32
+ description: "Number of SSE events received",
33
+ unit: "1",
34
+ });
35
+ this.durationHistogram = meter.createHistogram("cadreen.client.request_duration", {
36
+ description: "Request duration in milliseconds",
37
+ unit: "ms",
38
+ });
39
+ }
40
+ startSpan(name, options) {
41
+ const span = this.tracer.startSpan(name, {
42
+ attributes: options?.attributes,
43
+ });
44
+ if (options?.parent) {
45
+ span.setAttribute("parent.trace_id", options.parent.traceId);
46
+ span.setAttribute("parent.span_id", options.parent.spanId);
47
+ }
48
+ return new OtelSpanAdapter(span);
49
+ }
50
+ getMeter() {
51
+ return new OtelMeterAdapter(this.requestCounter, this.retryCounter, this.streamCounter, this.durationHistogram);
52
+ }
53
+ }
54
+ class OtelSpanAdapter {
55
+ constructor(span) {
56
+ this.span = span;
57
+ }
58
+ setName(name) {
59
+ this.span.updateName(name);
60
+ }
61
+ setAttribute(key, value) {
62
+ this.span.setAttribute(key, value);
63
+ }
64
+ setStatus(status) {
65
+ if (status === "error") {
66
+ this.span.setStatus({ code: 2 });
67
+ }
68
+ else {
69
+ this.span.setStatus({ code: 1 });
70
+ }
71
+ }
72
+ end() {
73
+ this.span.end();
74
+ }
75
+ }
76
+ class OtelMeterAdapter {
77
+ constructor(requestCounter, retryCounter, streamCounter, durationHistogram) {
78
+ this.requestCounter = requestCounter;
79
+ this.retryCounter = retryCounter;
80
+ this.streamCounter = streamCounter;
81
+ this.durationHistogram = durationHistogram;
82
+ }
83
+ recordRequest(method, path, status, durationMs) {
84
+ this.requestCounter.add(1, { "http.method": method, "http.url": path, "http.status_code": status });
85
+ this.durationHistogram.record(durationMs, { "http.method": method, "http.url": path, "http.status_code": status });
86
+ }
87
+ recordRetry(method, path, attempt) {
88
+ this.retryCounter.add(1, { "http.method": method, "http.url": path, attempt });
89
+ }
90
+ recordStreamEvent(eventType) {
91
+ this.streamCounter.add(1, { "event.type": eventType });
92
+ }
93
+ }
94
+ export function wrapWithTelemetry(provider) {
95
+ return {
96
+ onRequestStart(method, path) {
97
+ const span = provider.startSpan(`cadreen.${method.toLowerCase()}`, {
98
+ attributes: {
99
+ "http.method": method,
100
+ "http.url": path,
101
+ "cadreen.version": "2026-06-03",
102
+ },
103
+ });
104
+ return span;
105
+ },
106
+ onRequestEnd(span, method, path, status, durationMs) {
107
+ span.setAttribute("http.status_code", status);
108
+ span.setStatus(status < 400 ? "ok" : "error");
109
+ span.end();
110
+ provider.getMeter().recordRequest(method, path, status, durationMs);
111
+ },
112
+ onRetry(method, path, attempt) {
113
+ provider.getMeter().recordRetry(method, path, attempt);
114
+ },
115
+ onStreamEvent(eventType) {
116
+ provider.getMeter().recordStreamEvent(eventType);
117
+ },
118
+ onError(span, error) {
119
+ span.setAttribute("cadreen.error.code", error.code);
120
+ span.setAttribute("cadreen.error.type", error.errorType);
121
+ span.setStatus("error");
122
+ span.end();
123
+ },
124
+ };
125
+ }