@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/README.md
CHANGED
|
@@ -55,9 +55,9 @@ await struct.agent({ name: "checkout" }, async () => {
|
|
|
55
55
|
|
|
56
56
|
| Library | Hook | Span type | Notes |
|
|
57
57
|
|---|---|---|---|
|
|
58
|
-
| `@anthropic-ai/sdk` | `Messages.prototype.create`, `.stream` | `chat {model}` | Cache-token accounting, streaming with tool-use reconstruction |
|
|
58
|
+
| `@anthropic-ai/sdk` | `Messages.prototype.create`, `.stream` | `chat {model}` | Cache-token accounting, streaming with tool-use reconstruction. Defers to a LangChain `BaseChatModel` call already in progress (e.g. `ChatAnthropic`) so the two integrations don't double-emit — see ownership order below |
|
|
59
59
|
| `@anthropic-ai/bedrock-sdk`, `@anthropic-ai/vertex-sdk` | `Messages.prototype.*` | `chat {model}` | Best-effort, if installed |
|
|
60
|
-
| `@langchain/core` `BaseChatModel` | `.invoke`, `.stream` | `chat {model}` |
|
|
60
|
+
| `@langchain/core` `BaseChatModel` | `.invoke`, `.stream` | `chat {model}` | Always owns the `chat` span for `ChatAnthropic`/etc. calls, even when a provider-direct instrumentor (e.g. the Anthropic patch) is also active — the framework layer outranks the provider layer (single span; see [AGENTS.md](./AGENTS.md) §5) |
|
|
61
61
|
| `@langchain/core` `StructuredTool` | `.invoke` | `execute_tool {name}` | Extracts `tool_call_id` from LangChain ToolCall input or pending queue |
|
|
62
62
|
| `@langchain/core` `BaseRetriever` | `.invoke` | `retrieval {name}` | |
|
|
63
63
|
| `@langchain/langgraph` `Pregel` | `.invoke`, `.stream` | `invoke_agent {name}` | Covers `createReactAgent` and custom graphs. Reads conversation id from any of: `configurable.thread_id` (LangGraph canonical), or `metadata.{thread_id, session_id, conversation_id}` (LangSmith conventions). For multi-turn HTTP-style threading, wrap your entry point in [`struct.agent({ sessionId: convId }, ...)`](#recommended-pattern-wrap-langchain-entry-points-in-structagent) — the struct-native replacement for LangSmith's `tracing_context(parent=run_tree)`. |
|
|
@@ -189,8 +189,8 @@ await struct.agent({ name: "my-agent" }, async () => {
|
|
|
189
189
|
```
|
|
190
190
|
|
|
191
191
|
When you do use `ChatAnthropic` *and* have `@anthropic-ai/sdk` installed,
|
|
192
|
-
the chat span comes from the
|
|
193
|
-
|
|
192
|
+
the chat span comes from the LangChain layer (single span); the Anthropic
|
|
193
|
+
patch detects the framework already owns the call and defers.
|
|
194
194
|
|
|
195
195
|
## Content capture
|
|
196
196
|
|
|
@@ -264,6 +264,36 @@ Note: `gen_ai.usage.input_tokens` for Anthropic is the TRUE total — we add bac
|
|
|
264
264
|
`cache_read_input_tokens + cache_creation_input_tokens` (which Anthropic's raw
|
|
265
265
|
response excludes). Matches the Python SDK.
|
|
266
266
|
|
|
267
|
+
## Development
|
|
268
|
+
|
|
269
|
+
- `pnpm test` — unit + e2e tests (mocked, no network, no API keys). Excludes
|
|
270
|
+
`test/live/**`.
|
|
271
|
+
- `pnpm typecheck` — `tsc --noEmit`.
|
|
272
|
+
- `pnpm build` — `tshy`, producing the dual ESM/CJS `dist/`.
|
|
273
|
+
- `pnpm test:live` — the live real-model suite: real `@langchain/anthropic` +
|
|
274
|
+
real Anthropic API + real `@langchain/langgraph`, verifying emitted spans
|
|
275
|
+
in-memory (no ingester needed). Skips cleanly (exit 0, all tests skipped)
|
|
276
|
+
when no API key is present — safe to leave in normal CI.
|
|
277
|
+
|
|
278
|
+
To actually run it, point `STRUCT_LIVE_ENV_FILE` at a file containing
|
|
279
|
+
`ANTHROPIC_API_KEY=...` (dotenv-style `KEY=value` lines; quotes optional).
|
|
280
|
+
The path is never committed to the repo — it's supplied per-invocation:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
STRUCT_LIVE_ENV_FILE=/path/to/your/.env pnpm test:live
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Uses the cheapest current Anthropic model alias with small `max_tokens`
|
|
287
|
+
budgets — a handful of real API calls per run, not dozens.
|
|
288
|
+
- `pnpm test:parity` — the cross-language conformance harness: drives the
|
|
289
|
+
same canonical agent topology through both this SDK and `struct-sdk-python`
|
|
290
|
+
and diffs their normalized span shapes, failing (non-zero exit) on any
|
|
291
|
+
divergence. No network calls or API keys required.
|
|
292
|
+
|
|
293
|
+
See [AGENTS.md](./AGENTS.md) for the full set of governance rules (fault
|
|
294
|
+
isolation, OTel citizenship, semconv conformance, the release-version-bump
|
|
295
|
+
gate) that apply to any change in this package.
|
|
296
|
+
|
|
267
297
|
## Troubleshooting
|
|
268
298
|
|
|
269
299
|
- **Spans missing after instrumenting:** Import `@struct-ai/sdk` (or `struct.init()`)
|
|
@@ -273,10 +303,20 @@ response excludes). Matches the Python SDK.
|
|
|
273
303
|
- **No logs appearing:** `LogRecord`s only emit when `sdk.emitEvents` is true
|
|
274
304
|
(`EventOnly` or `SpanAndEvent` capture mode, which is the default). If you set
|
|
275
305
|
`captureContent: false` you disable them.
|
|
276
|
-
- **Duplicate chat spans:**
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
306
|
+
- **Duplicate chat spans:** Ownership of the `chat` span is fixed (manual >
|
|
307
|
+
framework > provider — see [AGENTS.md](./AGENTS.md) §5): when you call
|
|
308
|
+
`ChatAnthropic.invoke()`/`.stream()`, the LangChain integration always owns
|
|
309
|
+
the `chat` span (`handleChatModelStart` in `langchain-callback.ts`). It runs
|
|
310
|
+
the underlying provider call inside an internal scope
|
|
311
|
+
(`suppressGenAi: true`) that the direct `@anthropic-ai/sdk` patch checks via
|
|
312
|
+
`isGenAiSuppressed()` — when set, the provider patch defers and emits
|
|
313
|
+
nothing, so only the LangChain-owned span is produced. If you see doubles,
|
|
314
|
+
the two integrations are likely observing different `@anthropic-ai/sdk`
|
|
315
|
+
module instances (common with pnpm hoisting a nested copy under
|
|
316
|
+
`@langchain/anthropic`), so the provider patch never sees the suppression
|
|
317
|
+
scope set by the framework layer; confirm both integrations are
|
|
318
|
+
auto-instrumenting the SAME module instance (check `struct.initialized` and
|
|
319
|
+
your lockfile's dedupe of `@anthropic-ai/sdk`).
|
|
280
320
|
- **Subagent in a different trace / missing from parent's "Subagents" list:**
|
|
281
321
|
If you invoke a nested agent (`subagent.invoke(...)`) from inside a tool body,
|
|
282
322
|
define the outer tool with `tool(func, { name, description, schema })` from
|
|
@@ -4,12 +4,38 @@ export interface StructContext {
|
|
|
4
4
|
conversationId?: string;
|
|
5
5
|
agentSpan?: Span;
|
|
6
6
|
pendingToolCalls?: Record<string, string[]>;
|
|
7
|
+
/**
|
|
8
|
+
* When true, a framework-layer integration (e.g. LangChain's
|
|
9
|
+
* BaseChatModel.generate/.stream) already owns the chat span for the call
|
|
10
|
+
* in progress. Provider-SDK patches (anthropic.ts) check this and skip
|
|
11
|
+
* emitting their own chat span to avoid a duplicate.
|
|
12
|
+
*/
|
|
13
|
+
suppressGenAi?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Set by `struct.agent()` while its body is running. Signals ownership:
|
|
16
|
+
* when the LangChain callback handler sees a TOP-LEVEL chain start (no
|
|
17
|
+
* parentRunId) while this is set, the manual `struct.agent()` call already
|
|
18
|
+
* owns the `invoke_agent` span for this run — the handler must not emit a
|
|
19
|
+
* duplicate ("twin") span, only register the run so descendants parent
|
|
20
|
+
* under the manual span. Parity: python `_manual_agent_active` contextvar
|
|
21
|
+
* (core.py:64-68).
|
|
22
|
+
*/
|
|
23
|
+
manualAgentSpan?: Span;
|
|
7
24
|
}
|
|
8
25
|
export declare function getStore(): StructContext | undefined;
|
|
9
26
|
export declare function getSessionId(): string | undefined;
|
|
10
27
|
export declare function getConversationId(): string | undefined;
|
|
11
28
|
export declare function getAgentSpan(): Span | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* The manually-created `struct.agent()` span for the currently-running
|
|
31
|
+
* scope, if any. Read by the LangChain callback handler to detect
|
|
32
|
+
* "manual struct.agent() wraps a top-level LangChain chain" and suppress
|
|
33
|
+
* the twin `invoke_agent` span it would otherwise emit.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getManualAgentSpan(): Span | undefined;
|
|
12
36
|
export declare function getPendingToolCalls(): Record<string, string[]> | undefined;
|
|
37
|
+
/** True when a framework layer (e.g. LangChain) already owns the chat span. */
|
|
38
|
+
export declare function isGenAiSuppressed(): boolean;
|
|
13
39
|
export declare function runWithContext<T>(patch: Partial<StructContext>, fn: () => T): T;
|
|
14
40
|
export declare function ensurePendingToolCallsSlot(): Record<string, string[]>;
|
|
15
41
|
/**
|
package/dist/commonjs/context.js
CHANGED
|
@@ -4,7 +4,9 @@ exports.getStore = getStore;
|
|
|
4
4
|
exports.getSessionId = getSessionId;
|
|
5
5
|
exports.getConversationId = getConversationId;
|
|
6
6
|
exports.getAgentSpan = getAgentSpan;
|
|
7
|
+
exports.getManualAgentSpan = getManualAgentSpan;
|
|
7
8
|
exports.getPendingToolCalls = getPendingToolCalls;
|
|
9
|
+
exports.isGenAiSuppressed = isGenAiSuppressed;
|
|
8
10
|
exports.runWithContext = runWithContext;
|
|
9
11
|
exports.ensurePendingToolCallsSlot = ensurePendingToolCallsSlot;
|
|
10
12
|
exports.popPendingToolCallId = popPendingToolCallId;
|
|
@@ -26,9 +28,22 @@ function getConversationId() {
|
|
|
26
28
|
function getAgentSpan() {
|
|
27
29
|
return als.getStore()?.agentSpan;
|
|
28
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The manually-created `struct.agent()` span for the currently-running
|
|
33
|
+
* scope, if any. Read by the LangChain callback handler to detect
|
|
34
|
+
* "manual struct.agent() wraps a top-level LangChain chain" and suppress
|
|
35
|
+
* the twin `invoke_agent` span it would otherwise emit.
|
|
36
|
+
*/
|
|
37
|
+
function getManualAgentSpan() {
|
|
38
|
+
return als.getStore()?.manualAgentSpan;
|
|
39
|
+
}
|
|
29
40
|
function getPendingToolCalls() {
|
|
30
41
|
return als.getStore()?.pendingToolCalls;
|
|
31
42
|
}
|
|
43
|
+
/** True when a framework layer (e.g. LangChain) already owns the chat span. */
|
|
44
|
+
function isGenAiSuppressed() {
|
|
45
|
+
return als.getStore()?.suppressGenAi === true;
|
|
46
|
+
}
|
|
32
47
|
function runWithContext(patch, fn) {
|
|
33
48
|
const current = als.getStore() ?? {};
|
|
34
49
|
const next = { ...current, ...patch };
|
|
@@ -80,7 +95,11 @@ function pushPendingToolCalls(pairs) {
|
|
|
80
95
|
/** Snapshot the store for wrapping async iterators. */
|
|
81
96
|
function snapshotStore() {
|
|
82
97
|
const store = als.getStore();
|
|
83
|
-
|
|
98
|
+
if (!store)
|
|
99
|
+
return undefined;
|
|
100
|
+
if (!store.pendingToolCalls)
|
|
101
|
+
store.pendingToolCalls = {}; // materialize BEFORE copying so the queue object is shared by reference
|
|
102
|
+
return { ...store };
|
|
84
103
|
}
|
|
85
104
|
function runWithStore(store, fn) {
|
|
86
105
|
if (!store)
|
package/dist/commonjs/core.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.StructSDK = exports.firstFailureLogged = exports.DEFAULT_ENDPOINT = void 0;
|
|
4
4
|
exports.safe = safe;
|
|
5
|
-
const node_crypto_1 = require("node:crypto");
|
|
6
5
|
const api_1 = require("@opentelemetry/api");
|
|
7
6
|
const exporter_logs_otlp_proto_1 = require("@opentelemetry/exporter-logs-otlp-proto");
|
|
8
7
|
const exporter_trace_otlp_proto_1 = require("@opentelemetry/exporter-trace-otlp-proto");
|
|
@@ -14,6 +13,7 @@ const context_js_1 = require("./context.js");
|
|
|
14
13
|
const index_js_1 = require("./integrations/index.js");
|
|
15
14
|
const semconv_js_1 = require("./semconv.js");
|
|
16
15
|
const truncation_js_1 = require("./truncation.js");
|
|
16
|
+
const version_js_1 = require("./version.js");
|
|
17
17
|
exports.DEFAULT_ENDPOINT = "https://ingest.struct.ai";
|
|
18
18
|
const LOG_ENABLED = process.env.STRUCT_SDK_LOG === "1";
|
|
19
19
|
const defaultInternalLogger = {
|
|
@@ -232,13 +232,13 @@ class StructSDK {
|
|
|
232
232
|
if (!this._tracerProvider) {
|
|
233
233
|
throw new Error("Call struct.init() before using the SDK");
|
|
234
234
|
}
|
|
235
|
-
return this._tracerProvider.getTracer(name);
|
|
235
|
+
return this._tracerProvider.getTracer(name, version_js_1.SDK_VERSION);
|
|
236
236
|
}
|
|
237
237
|
getLogger(name = "struct-sdk") {
|
|
238
238
|
if (!this._loggerProvider) {
|
|
239
239
|
throw new Error("Call struct.init() before using the SDK");
|
|
240
240
|
}
|
|
241
|
-
return this._loggerProvider.getLogger(name);
|
|
241
|
+
return this._loggerProvider.getLogger(name, version_js_1.SDK_VERSION);
|
|
242
242
|
}
|
|
243
243
|
getInternalLogger() {
|
|
244
244
|
return this._internalLogger;
|
|
@@ -306,8 +306,7 @@ class StructSDK {
|
|
|
306
306
|
return await fn();
|
|
307
307
|
}
|
|
308
308
|
const agentName = options.name;
|
|
309
|
-
const
|
|
310
|
-
const parentSessionId = (0, context_js_1.getSessionId)();
|
|
309
|
+
const explicitSessionId = options.sessionId;
|
|
311
310
|
const tracer = this.getTracer("struct-sdk");
|
|
312
311
|
// Parent the new agent span on the nearest enclosing agent span (if
|
|
313
312
|
// any) so nested `struct.agent()` / `struct.tool()` calls build a
|
|
@@ -316,16 +315,68 @@ class StructSDK {
|
|
|
316
315
|
// nothing to hang off. We do NOT rely on the global OTel context
|
|
317
316
|
// manager being installed — the SDK manages its own parent chain via
|
|
318
317
|
// StructContext ALS to stay neutral in multi-tenant OTel setups.
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
318
|
+
//
|
|
319
|
+
// Captured BEFORE we resolve/overwrite anything below — mirrors
|
|
320
|
+
// python's `enclosing_session_id` / `enclosing_agent_span` capture at
|
|
321
|
+
// core.py:625-626. Used to (1) resolve this agent's own session id via
|
|
322
|
+
// ambient inheritance, (2) detect break-out, and (3) decide whether to
|
|
323
|
+
// stamp `struct.agent.parent_session_id`.
|
|
324
|
+
const enclosingSessionId = (0, context_js_1.getSessionId)();
|
|
325
|
+
const enclosingAgentSpan = (0, context_js_1.getAgentSpan)();
|
|
326
|
+
// ── Resolve sessionId (REVISION R1 grouping model) ──────────────────
|
|
327
|
+
// Resolution order: explicit caller arg > ambient (enclosing agent) >
|
|
328
|
+
// session-less. We never fabricate a uuid — a caller that omits
|
|
329
|
+
// sessionId with no enclosing agent gets a SESSION-LESS run (no
|
|
330
|
+
// `gen_ai.conversation.id` attribute at all). Ports core.py:628-644
|
|
331
|
+
// exactly (`self._session_id = ""` there is `undefined` here).
|
|
332
|
+
const sessionId = explicitSessionId !== undefined
|
|
333
|
+
? explicitSessionId
|
|
334
|
+
: enclosingSessionId !== undefined
|
|
335
|
+
? enclosingSessionId
|
|
336
|
+
: undefined;
|
|
337
|
+
// ── Break-out detection (spawned-by Link) ───────────────────────────
|
|
338
|
+
// Caller supplied an EXPLICIT session id that differs from the
|
|
339
|
+
// enclosing agent's session AND there IS an enclosing Struct agent
|
|
340
|
+
// span in scope. This agent starts a fresh ROOT trace (no OTel
|
|
341
|
+
// parent) and carries a causal Link back to the enclosing agent's
|
|
342
|
+
// span context. Ports struct_sdk.core._AgentContext._start_span's
|
|
343
|
+
// `break_out` branch (struct-sdk-python/src/struct_sdk/core.py:651-656)
|
|
344
|
+
// exactly.
|
|
345
|
+
const breakOut = explicitSessionId !== undefined &&
|
|
346
|
+
enclosingSessionId !== undefined &&
|
|
347
|
+
explicitSessionId !== enclosingSessionId &&
|
|
348
|
+
enclosingAgentSpan !== undefined;
|
|
349
|
+
// ── Parentage: PURE NEST (Option D) ─────────────────────────────────
|
|
350
|
+
// A non-break-out agent nests under whatever OTel span is active — the
|
|
351
|
+
// host's ambient context — like every peer LLM/agent SDK. We do NOT
|
|
352
|
+
// second-guess the active span or unilaterally re-root: the old
|
|
353
|
+
// `foreignRoot` heuristic detached from ANY active span and so ripped
|
|
354
|
+
// agents out of legitimate host request traces. Cross-conversation
|
|
355
|
+
// grouping is carried by gen_ai.conversation.id, never by trace
|
|
356
|
+
// parentage. A runtime that propagates a stale/unwanted span across a
|
|
357
|
+
// boundary (e.g. our own persistent_agent Temporal workflow span) clears
|
|
358
|
+
// it at the SOURCE, never here. (A provenance-gated detach — re-root only
|
|
359
|
+
// a leaked Struct-OWNED span — is a documented, deferred follow-up.)
|
|
360
|
+
let startContext;
|
|
361
|
+
let links;
|
|
362
|
+
if (breakOut) {
|
|
363
|
+
// Explicit different session under another Struct agent: fresh ROOT
|
|
364
|
+
// trace + a spawned-by Link. The one deliberate, opt-in re-root.
|
|
365
|
+
// enclosingAgentSpan is guaranteed defined by the breakOut guard above.
|
|
366
|
+
links = [{ context: enclosingAgentSpan.spanContext() }];
|
|
367
|
+
startContext = api_1.ROOT_CONTEXT;
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
startContext = enclosingAgentSpan
|
|
371
|
+
? api_1.trace.setSpan(api_1.context.active(), enclosingAgentSpan)
|
|
372
|
+
: api_1.context.active();
|
|
373
|
+
}
|
|
323
374
|
// Span creation itself can fail (custom tracer, broken context). If it
|
|
324
375
|
// does, fall through to running `fn` directly so the user's call
|
|
325
376
|
// always runs uninstrumented rather than blowing up on telemetry.
|
|
326
377
|
let span;
|
|
327
378
|
safe(() => {
|
|
328
|
-
span = tracer.startSpan(`invoke_agent ${agentName}`, { kind: api_1.SpanKind.INTERNAL },
|
|
379
|
+
span = tracer.startSpan(`invoke_agent ${agentName}`, { kind: api_1.SpanKind.INTERNAL, links }, startContext);
|
|
329
380
|
}, "agent.start_span", this._internalLogger);
|
|
330
381
|
if (span === undefined) {
|
|
331
382
|
return await fn();
|
|
@@ -346,17 +397,24 @@ class StructSDK {
|
|
|
346
397
|
startedSpan.setAttribute(semconv_js_1.GEN_AI.AGENT_VERSION, options.version);
|
|
347
398
|
}
|
|
348
399
|
// gen_ai.conversation.id is the spec-blessed name for
|
|
349
|
-
// session/thread id.
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
|
|
400
|
+
// session/thread id. Omitted when session-less (never fabricated).
|
|
401
|
+
// Ports core.py:723-725 (`if self._session_id:`) exactly.
|
|
402
|
+
if (sessionId) {
|
|
403
|
+
startedSpan.setAttribute(semconv_js_1.GEN_AI.CONVERSATION_ID, sessionId);
|
|
404
|
+
}
|
|
405
|
+
// ``struct.agent.parent_session_id`` is a SPAWNED-BY marker — set it
|
|
406
|
+
// ONLY when this agent has an enclosing session that DIFFERS from
|
|
407
|
+
// its own resolved session id. An inline nested agent (same session
|
|
408
|
+
// as the enclosing agent, whether by explicit same id or ambient
|
|
409
|
+
// inheritance) already has its parent relationship encoded by the
|
|
410
|
+
// OTel span tree (ParentSpanId) — stamping
|
|
411
|
+
// parent_session_id === own sessionId would be self-referential
|
|
412
|
+
// noise. Structure comes from the tree/Link, not this attr
|
|
413
|
+
// (Link-canonical decision). Ports core.py:736-745 exactly (both
|
|
414
|
+
// the break-out and the "legacy" differing-id-no-span branches
|
|
415
|
+
// collapse to this one condition).
|
|
416
|
+
if (enclosingSessionId !== undefined && enclosingSessionId !== sessionId) {
|
|
417
|
+
startedSpan.setAttribute(semconv_js_1.STRUCT.AGENT_PARENT_SESSION_ID, enclosingSessionId);
|
|
360
418
|
}
|
|
361
419
|
if (options.metadata) {
|
|
362
420
|
for (const [key, value] of Object.entries(options.metadata)) {
|
|
@@ -369,6 +427,7 @@ class StructSDK {
|
|
|
369
427
|
conversationId: sessionId,
|
|
370
428
|
agentSpan: startedSpan,
|
|
371
429
|
pendingToolCalls: {},
|
|
430
|
+
manualAgentSpan: startedSpan,
|
|
372
431
|
}, async () => {
|
|
373
432
|
const activeCtx = api_1.trace.setSpan(api_1.context.active(), startedSpan);
|
|
374
433
|
try {
|
package/dist/commonjs/events.js
CHANGED
|
@@ -53,7 +53,7 @@ function emitAnthropicMessageEvents(logger, messages, system) {
|
|
|
53
53
|
parts: (0, truncation_js_1.truncateParts)(parts),
|
|
54
54
|
}),
|
|
55
55
|
extraAttrs: {
|
|
56
|
-
[semconv_js_1.GEN_AI.
|
|
56
|
+
[semconv_js_1.GEN_AI.PROVIDER_NAME]: "anthropic",
|
|
57
57
|
[semconv_js_1.GEN_AI.MESSAGE_INDEX]: msgIndex,
|
|
58
58
|
},
|
|
59
59
|
});
|
|
@@ -71,7 +71,7 @@ function emitAnthropicMessageEvents(logger, messages, system) {
|
|
|
71
71
|
eventName,
|
|
72
72
|
payload: (0, truncation_js_1.safeJsonStringify)({ role, parts: (0, truncation_js_1.truncateParts)(parts) }),
|
|
73
73
|
extraAttrs: {
|
|
74
|
-
[semconv_js_1.GEN_AI.
|
|
74
|
+
[semconv_js_1.GEN_AI.PROVIDER_NAME]: "anthropic",
|
|
75
75
|
[semconv_js_1.GEN_AI.MESSAGE_INDEX]: msgIndex,
|
|
76
76
|
},
|
|
77
77
|
});
|
|
@@ -96,7 +96,7 @@ function emitAnthropicChoiceEvent(logger, contentBlocks, stopReason) {
|
|
|
96
96
|
eventName: semconv_js_1.EVENT_NAMES.CHOICE,
|
|
97
97
|
payload,
|
|
98
98
|
extraAttrs: {
|
|
99
|
-
[semconv_js_1.GEN_AI.
|
|
99
|
+
[semconv_js_1.GEN_AI.PROVIDER_NAME]: "anthropic",
|
|
100
100
|
},
|
|
101
101
|
});
|
|
102
102
|
}
|
|
@@ -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 */
|