@struct-ai/sdk 0.3.0 → 0.3.17
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 +48 -8
- package/dist/commonjs/context.d.ts +26 -0
- package/dist/commonjs/context.js +20 -1
- package/dist/commonjs/core.js +80 -21
- package/dist/commonjs/events.js +3 -3
- package/dist/commonjs/integrations/anthropic.d.ts +2 -0
- package/dist/commonjs/integrations/anthropic.js +470 -47
- package/dist/commonjs/integrations/langchain-callback.d.ts +179 -27
- package/dist/commonjs/integrations/langchain-callback.js +670 -81
- package/dist/commonjs/integrations/langchain.d.ts +3 -0
- package/dist/commonjs/integrations/langchain.js +353 -7
- package/dist/commonjs/semconv.d.ts +11 -0
- package/dist/commonjs/semconv.js +12 -1
- package/dist/commonjs/version.d.ts +2 -0
- package/dist/commonjs/version.js +6 -0
- package/dist/esm/context.d.ts +26 -0
- package/dist/esm/context.js +18 -1
- package/dist/esm/core.js +81 -22
- package/dist/esm/events.js +3 -3
- package/dist/esm/integrations/anthropic.d.ts +2 -0
- package/dist/esm/integrations/anthropic.js +471 -50
- package/dist/esm/integrations/langchain-callback.d.ts +179 -27
- package/dist/esm/integrations/langchain-callback.js +672 -83
- package/dist/esm/integrations/langchain.d.ts +3 -0
- package/dist/esm/integrations/langchain.js +352 -7
- package/dist/esm/semconv.d.ts +11 -0
- package/dist/esm/semconv.js +11 -0
- package/dist/esm/version.d.ts +2 -0
- package/dist/esm/version.js +3 -0
- package/package.json +4 -2
package/dist/esm/core.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { context as otelContext, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
|
|
1
|
+
import { context as otelContext, ROOT_CONTEXT, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
|
|
3
2
|
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-proto";
|
|
4
3
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
|
|
5
4
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
@@ -10,6 +9,7 @@ import { getAgentSpan, getSessionId, popPendingToolCallId, runWithContext, } fro
|
|
|
10
9
|
import { autoInstrument } from "./integrations/index.js";
|
|
11
10
|
import { ERROR_TYPE, GEN_AI, STRUCT, } from "./semconv.js";
|
|
12
11
|
import { safeJsonStringify } from "./truncation.js";
|
|
12
|
+
import { SDK_VERSION } from "./version.js";
|
|
13
13
|
export const DEFAULT_ENDPOINT = "https://ingest.struct.ai";
|
|
14
14
|
const LOG_ENABLED = process.env.STRUCT_SDK_LOG === "1";
|
|
15
15
|
const defaultInternalLogger = {
|
|
@@ -228,13 +228,13 @@ export class StructSDK {
|
|
|
228
228
|
if (!this._tracerProvider) {
|
|
229
229
|
throw new Error("Call struct.init() before using the SDK");
|
|
230
230
|
}
|
|
231
|
-
return this._tracerProvider.getTracer(name);
|
|
231
|
+
return this._tracerProvider.getTracer(name, SDK_VERSION);
|
|
232
232
|
}
|
|
233
233
|
getLogger(name = "struct-sdk") {
|
|
234
234
|
if (!this._loggerProvider) {
|
|
235
235
|
throw new Error("Call struct.init() before using the SDK");
|
|
236
236
|
}
|
|
237
|
-
return this._loggerProvider.getLogger(name);
|
|
237
|
+
return this._loggerProvider.getLogger(name, SDK_VERSION);
|
|
238
238
|
}
|
|
239
239
|
getInternalLogger() {
|
|
240
240
|
return this._internalLogger;
|
|
@@ -302,8 +302,7 @@ export class StructSDK {
|
|
|
302
302
|
return await fn();
|
|
303
303
|
}
|
|
304
304
|
const agentName = options.name;
|
|
305
|
-
const
|
|
306
|
-
const parentSessionId = getSessionId();
|
|
305
|
+
const explicitSessionId = options.sessionId;
|
|
307
306
|
const tracer = this.getTracer("struct-sdk");
|
|
308
307
|
// Parent the new agent span on the nearest enclosing agent span (if
|
|
309
308
|
// any) so nested `struct.agent()` / `struct.tool()` calls build a
|
|
@@ -312,16 +311,68 @@ export class StructSDK {
|
|
|
312
311
|
// nothing to hang off. We do NOT rely on the global OTel context
|
|
313
312
|
// manager being installed — the SDK manages its own parent chain via
|
|
314
313
|
// StructContext ALS to stay neutral in multi-tenant OTel setups.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
314
|
+
//
|
|
315
|
+
// Captured BEFORE we resolve/overwrite anything below — mirrors
|
|
316
|
+
// python's `enclosing_session_id` / `enclosing_agent_span` capture at
|
|
317
|
+
// core.py:625-626. Used to (1) resolve this agent's own session id via
|
|
318
|
+
// ambient inheritance, (2) detect break-out, and (3) decide whether to
|
|
319
|
+
// stamp `struct.agent.parent_session_id`.
|
|
320
|
+
const enclosingSessionId = getSessionId();
|
|
321
|
+
const enclosingAgentSpan = getAgentSpan();
|
|
322
|
+
// ── Resolve sessionId (REVISION R1 grouping model) ──────────────────
|
|
323
|
+
// Resolution order: explicit caller arg > ambient (enclosing agent) >
|
|
324
|
+
// session-less. We never fabricate a uuid — a caller that omits
|
|
325
|
+
// sessionId with no enclosing agent gets a SESSION-LESS run (no
|
|
326
|
+
// `gen_ai.conversation.id` attribute at all). Ports core.py:628-644
|
|
327
|
+
// exactly (`self._session_id = ""` there is `undefined` here).
|
|
328
|
+
const sessionId = explicitSessionId !== undefined
|
|
329
|
+
? explicitSessionId
|
|
330
|
+
: enclosingSessionId !== undefined
|
|
331
|
+
? enclosingSessionId
|
|
332
|
+
: undefined;
|
|
333
|
+
// ── Break-out detection (spawned-by Link) ───────────────────────────
|
|
334
|
+
// Caller supplied an EXPLICIT session id that differs from the
|
|
335
|
+
// enclosing agent's session AND there IS an enclosing Struct agent
|
|
336
|
+
// span in scope. This agent starts a fresh ROOT trace (no OTel
|
|
337
|
+
// parent) and carries a causal Link back to the enclosing agent's
|
|
338
|
+
// span context. Ports struct_sdk.core._AgentContext._start_span's
|
|
339
|
+
// `break_out` branch (struct-sdk-python/src/struct_sdk/core.py:651-656)
|
|
340
|
+
// exactly.
|
|
341
|
+
const breakOut = explicitSessionId !== undefined &&
|
|
342
|
+
enclosingSessionId !== undefined &&
|
|
343
|
+
explicitSessionId !== enclosingSessionId &&
|
|
344
|
+
enclosingAgentSpan !== undefined;
|
|
345
|
+
// ── Parentage: PURE NEST (Option D) ─────────────────────────────────
|
|
346
|
+
// A non-break-out agent nests under whatever OTel span is active — the
|
|
347
|
+
// host's ambient context — like every peer LLM/agent SDK. We do NOT
|
|
348
|
+
// second-guess the active span or unilaterally re-root: the old
|
|
349
|
+
// `foreignRoot` heuristic detached from ANY active span and so ripped
|
|
350
|
+
// agents out of legitimate host request traces. Cross-conversation
|
|
351
|
+
// grouping is carried by gen_ai.conversation.id, never by trace
|
|
352
|
+
// parentage. A runtime that propagates a stale/unwanted span across a
|
|
353
|
+
// boundary (e.g. our own persistent_agent Temporal workflow span) clears
|
|
354
|
+
// it at the SOURCE, never here. (A provenance-gated detach — re-root only
|
|
355
|
+
// a leaked Struct-OWNED span — is a documented, deferred follow-up.)
|
|
356
|
+
let startContext;
|
|
357
|
+
let links;
|
|
358
|
+
if (breakOut) {
|
|
359
|
+
// Explicit different session under another Struct agent: fresh ROOT
|
|
360
|
+
// trace + a spawned-by Link. The one deliberate, opt-in re-root.
|
|
361
|
+
// enclosingAgentSpan is guaranteed defined by the breakOut guard above.
|
|
362
|
+
links = [{ context: enclosingAgentSpan.spanContext() }];
|
|
363
|
+
startContext = ROOT_CONTEXT;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
startContext = enclosingAgentSpan
|
|
367
|
+
? trace.setSpan(otelContext.active(), enclosingAgentSpan)
|
|
368
|
+
: otelContext.active();
|
|
369
|
+
}
|
|
319
370
|
// Span creation itself can fail (custom tracer, broken context). If it
|
|
320
371
|
// does, fall through to running `fn` directly so the user's call
|
|
321
372
|
// always runs uninstrumented rather than blowing up on telemetry.
|
|
322
373
|
let span;
|
|
323
374
|
safe(() => {
|
|
324
|
-
span = tracer.startSpan(`invoke_agent ${agentName}`, { kind: SpanKind.INTERNAL },
|
|
375
|
+
span = tracer.startSpan(`invoke_agent ${agentName}`, { kind: SpanKind.INTERNAL, links }, startContext);
|
|
325
376
|
}, "agent.start_span", this._internalLogger);
|
|
326
377
|
if (span === undefined) {
|
|
327
378
|
return await fn();
|
|
@@ -342,17 +393,24 @@ export class StructSDK {
|
|
|
342
393
|
startedSpan.setAttribute(GEN_AI.AGENT_VERSION, options.version);
|
|
343
394
|
}
|
|
344
395
|
// gen_ai.conversation.id is the spec-blessed name for
|
|
345
|
-
// session/thread id.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
//
|
|
351
|
-
//
|
|
352
|
-
//
|
|
353
|
-
//
|
|
354
|
-
|
|
355
|
-
|
|
396
|
+
// session/thread id. Omitted when session-less (never fabricated).
|
|
397
|
+
// Ports core.py:723-725 (`if self._session_id:`) exactly.
|
|
398
|
+
if (sessionId) {
|
|
399
|
+
startedSpan.setAttribute(GEN_AI.CONVERSATION_ID, sessionId);
|
|
400
|
+
}
|
|
401
|
+
// ``struct.agent.parent_session_id`` is a SPAWNED-BY marker — set it
|
|
402
|
+
// ONLY when this agent has an enclosing session that DIFFERS from
|
|
403
|
+
// its own resolved session id. An inline nested agent (same session
|
|
404
|
+
// as the enclosing agent, whether by explicit same id or ambient
|
|
405
|
+
// inheritance) already has its parent relationship encoded by the
|
|
406
|
+
// OTel span tree (ParentSpanId) — stamping
|
|
407
|
+
// parent_session_id === own sessionId would be self-referential
|
|
408
|
+
// noise. Structure comes from the tree/Link, not this attr
|
|
409
|
+
// (Link-canonical decision). Ports core.py:736-745 exactly (both
|
|
410
|
+
// the break-out and the "legacy" differing-id-no-span branches
|
|
411
|
+
// collapse to this one condition).
|
|
412
|
+
if (enclosingSessionId !== undefined && enclosingSessionId !== sessionId) {
|
|
413
|
+
startedSpan.setAttribute(STRUCT.AGENT_PARENT_SESSION_ID, enclosingSessionId);
|
|
356
414
|
}
|
|
357
415
|
if (options.metadata) {
|
|
358
416
|
for (const [key, value] of Object.entries(options.metadata)) {
|
|
@@ -365,6 +423,7 @@ export class StructSDK {
|
|
|
365
423
|
conversationId: sessionId,
|
|
366
424
|
agentSpan: startedSpan,
|
|
367
425
|
pendingToolCalls: {},
|
|
426
|
+
manualAgentSpan: startedSpan,
|
|
368
427
|
}, async () => {
|
|
369
428
|
const activeCtx = trace.setSpan(otelContext.active(), startedSpan);
|
|
370
429
|
try {
|
package/dist/esm/events.js
CHANGED
|
@@ -49,7 +49,7 @@ export function emitAnthropicMessageEvents(logger, messages, system) {
|
|
|
49
49
|
parts: truncateParts(parts),
|
|
50
50
|
}),
|
|
51
51
|
extraAttrs: {
|
|
52
|
-
[GEN_AI.
|
|
52
|
+
[GEN_AI.PROVIDER_NAME]: "anthropic",
|
|
53
53
|
[GEN_AI.MESSAGE_INDEX]: msgIndex,
|
|
54
54
|
},
|
|
55
55
|
});
|
|
@@ -67,7 +67,7 @@ export function emitAnthropicMessageEvents(logger, messages, system) {
|
|
|
67
67
|
eventName,
|
|
68
68
|
payload: safeJsonStringify({ role, parts: truncateParts(parts) }),
|
|
69
69
|
extraAttrs: {
|
|
70
|
-
[GEN_AI.
|
|
70
|
+
[GEN_AI.PROVIDER_NAME]: "anthropic",
|
|
71
71
|
[GEN_AI.MESSAGE_INDEX]: msgIndex,
|
|
72
72
|
},
|
|
73
73
|
});
|
|
@@ -92,7 +92,7 @@ export function emitAnthropicChoiceEvent(logger, contentBlocks, stopReason) {
|
|
|
92
92
|
eventName: EVENT_NAMES.CHOICE,
|
|
93
93
|
payload,
|
|
94
94
|
extraAttrs: {
|
|
95
|
-
[GEN_AI.
|
|
95
|
+
[GEN_AI.PROVIDER_NAME]: "anthropic",
|
|
96
96
|
},
|
|
97
97
|
});
|
|
98
98
|
}
|
|
@@ -22,6 +22,8 @@ export declare function patch(sdk: StructSDK): Promise<void>;
|
|
|
22
22
|
export declare function unpatch(): Promise<void>;
|
|
23
23
|
type CreateMethod = (this: unknown, params: CreateParams, opts?: unknown) => unknown;
|
|
24
24
|
type StreamMethod = (this: unknown, params: CreateParams, opts?: unknown) => unknown;
|
|
25
|
+
export declare function wrapCreate(original: CreateMethod): CreateMethod;
|
|
26
|
+
export declare function wrapStream(original: StreamMethod): StreamMethod;
|
|
25
27
|
/** @internal */
|
|
26
28
|
export type _PatchContextForTest = PatchContext;
|
|
27
29
|
/** @internal */
|