@struct-ai/sdk 0.1.2 → 0.2.1

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.
Files changed (177) hide show
  1. package/README.md +46 -3
  2. package/dist/commonjs/core.js +8 -1
  3. package/dist/commonjs/integrations/langchain-callback.js +40 -5
  4. package/dist/esm/core.js +8 -1
  5. package/dist/esm/integrations/langchain-callback.js +40 -5
  6. package/package.json +16 -13
  7. package/dist/node_modules/@struct-ai/sdk/dist/commonjs/content-capture.d.ts +0 -10
  8. package/dist/node_modules/@struct-ai/sdk/dist/commonjs/context.d.ts +0 -30
  9. package/dist/node_modules/@struct-ai/sdk/dist/commonjs/core.d.ts +0 -120
  10. package/dist/node_modules/@struct-ai/sdk/dist/commonjs/events.d.ts +0 -12
  11. package/dist/node_modules/@struct-ai/sdk/dist/commonjs/index.d.ts +0 -6
  12. package/dist/node_modules/@struct-ai/sdk/dist/commonjs/integrations/anthropic-content.d.ts +0 -24
  13. package/dist/node_modules/@struct-ai/sdk/dist/commonjs/integrations/anthropic.d.ts +0 -34
  14. package/dist/node_modules/@struct-ai/sdk/dist/commonjs/integrations/index.d.ts +0 -5
  15. package/dist/node_modules/@struct-ai/sdk/dist/commonjs/integrations/langchain-callback.d.ts +0 -134
  16. package/dist/node_modules/@struct-ai/sdk/dist/commonjs/integrations/langchain-content.d.ts +0 -35
  17. package/dist/node_modules/@struct-ai/sdk/dist/commonjs/integrations/langchain.d.ts +0 -11
  18. package/dist/node_modules/@struct-ai/sdk/dist/commonjs/package.json +0 -3
  19. package/dist/node_modules/@struct-ai/sdk/dist/commonjs/semconv.d.ts +0 -54
  20. package/dist/node_modules/@struct-ai/sdk/dist/commonjs/truncation.d.ts +0 -10
  21. package/dist/node_modules/@struct-ai/sdk/dist/esm/content-capture.d.ts +0 -10
  22. package/dist/node_modules/@struct-ai/sdk/dist/esm/context.d.ts +0 -30
  23. package/dist/node_modules/@struct-ai/sdk/dist/esm/core.d.ts +0 -120
  24. package/dist/node_modules/@struct-ai/sdk/dist/esm/events.d.ts +0 -12
  25. package/dist/node_modules/@struct-ai/sdk/dist/esm/index.d.ts +0 -6
  26. package/dist/node_modules/@struct-ai/sdk/dist/esm/integrations/anthropic-content.d.ts +0 -24
  27. package/dist/node_modules/@struct-ai/sdk/dist/esm/integrations/anthropic.d.ts +0 -34
  28. package/dist/node_modules/@struct-ai/sdk/dist/esm/integrations/index.d.ts +0 -5
  29. package/dist/node_modules/@struct-ai/sdk/dist/esm/integrations/langchain-callback.d.ts +0 -134
  30. package/dist/node_modules/@struct-ai/sdk/dist/esm/integrations/langchain-content.d.ts +0 -35
  31. package/dist/node_modules/@struct-ai/sdk/dist/esm/integrations/langchain.d.ts +0 -11
  32. package/dist/node_modules/@struct-ai/sdk/dist/esm/package.json +0 -3
  33. package/dist/node_modules/@struct-ai/sdk/dist/esm/semconv.d.ts +0 -54
  34. package/dist/node_modules/@struct-ai/sdk/dist/esm/truncation.d.ts +0 -10
  35. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/dist/commonjs/package.json +0 -3
  36. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/dist/esm/package.json +0 -3
  37. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/package.json +0 -100
  38. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/package.json +0 -103
  39. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/anthropic/package.json +0 -118
  40. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/package.json +0 -916
  41. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/package.json +0 -188
  42. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/api/package.json +0 -96
  43. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/api-logs/package.json +0 -91
  44. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/core/package.json +0 -96
  45. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/exporter-logs-otlp-proto/package.json +0 -108
  46. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/exporter-trace-otlp-proto/package.json +0 -105
  47. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/resources/package.json +0 -100
  48. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/sdk-logs/package.json +0 -109
  49. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/sdk-trace-base/package.json +0 -100
  50. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/sdk-trace-node/package.json +0 -76
  51. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/package.json +0 -140
  52. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/nock/package.json +0 -89
  53. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/tshy/package.json +0 -81
  54. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/typescript/package.json +0 -120
  55. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/node_modules/vitest/package.json +0 -239
  56. package/dist/node_modules/@struct-ai/sdk/dist/node_modules/@struct-ai/sdk/package.json +0 -100
  57. package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/core.d.ts +0 -244
  58. package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/error.d.ts +0 -54
  59. package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/index.d.ts +0 -188
  60. package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/package.json +0 -103
  61. package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/pagination.d.ts +0 -26
  62. package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/resource.d.ts +0 -6
  63. package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/streaming.d.ts +0 -41
  64. package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/uploads.d.ts +0 -75
  65. package/dist/node_modules/@struct-ai/sdk/node_modules/@anthropic-ai/sdk/version.d.ts +0 -2
  66. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/anthropic/experimental.d.ts +0 -1
  67. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/anthropic/index.d.ts +0 -1
  68. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/anthropic/package.json +0 -118
  69. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/agents.d.ts +0 -1
  70. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/caches.d.ts +0 -1
  71. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/chat_history.d.ts +0 -1
  72. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/context.d.ts +0 -1
  73. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/documents.d.ts +0 -1
  74. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/embeddings.d.ts +0 -1
  75. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/example_selectors.d.ts +0 -1
  76. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/index.d.ts +0 -1
  77. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/indexing.d.ts +0 -1
  78. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/load.d.ts +0 -1
  79. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/memory.d.ts +0 -1
  80. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/messages.d.ts +0 -1
  81. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/output_parsers.d.ts +0 -1
  82. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/outputs.d.ts +0 -1
  83. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/package.json +0 -916
  84. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/prompt_values.d.ts +0 -1
  85. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/prompts.d.ts +0 -1
  86. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/retrievers.d.ts +0 -1
  87. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/runnables.d.ts +0 -1
  88. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/singletons.d.ts +0 -1
  89. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/stores.d.ts +0 -1
  90. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/structured_query.d.ts +0 -1
  91. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/tools.d.ts +0 -1
  92. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/core/vectorstores.d.ts +0 -1
  93. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/index.d.ts +0 -1
  94. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/package.json +0 -188
  95. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/prebuilt.d.ts +0 -1
  96. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/pregel.d.ts +0 -1
  97. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/remote.d.ts +0 -1
  98. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/web.d.ts +0 -1
  99. package/dist/node_modules/@struct-ai/sdk/node_modules/@langchain/langgraph/zod.d.ts +0 -1
  100. package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/api/package.json +0 -96
  101. package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/api-logs/package.json +0 -91
  102. package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/core/package.json +0 -96
  103. package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/exporter-logs-otlp-proto/package.json +0 -108
  104. package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/exporter-trace-otlp-proto/package.json +0 -105
  105. package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/resources/package.json +0 -100
  106. package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/sdk-logs/package.json +0 -109
  107. package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/sdk-trace-base/package.json +0 -100
  108. package/dist/node_modules/@struct-ai/sdk/node_modules/@opentelemetry/sdk-trace-node/package.json +0 -76
  109. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/assert.d.ts +0 -1062
  110. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/async_hooks.d.ts +0 -605
  111. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/buffer.buffer.d.ts +0 -471
  112. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/buffer.d.ts +0 -1936
  113. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/child_process.d.ts +0 -1475
  114. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/cluster.d.ts +0 -577
  115. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/console.d.ts +0 -452
  116. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/constants.d.ts +0 -21
  117. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/crypto.d.ts +0 -4590
  118. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/dgram.d.ts +0 -597
  119. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/diagnostics_channel.d.ts +0 -578
  120. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/dns.d.ts +0 -871
  121. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/domain.d.ts +0 -170
  122. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/events.d.ts +0 -977
  123. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/fs.d.ts +0 -4375
  124. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/globals.d.ts +0 -172
  125. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/globals.typedarray.d.ts +0 -38
  126. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/http.d.ts +0 -2049
  127. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/http2.d.ts +0 -2631
  128. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/https.d.ts +0 -578
  129. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/index.d.ts +0 -93
  130. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/inspector.generated.d.ts +0 -3966
  131. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/module.d.ts +0 -539
  132. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/net.d.ts +0 -1031
  133. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/os.d.ts +0 -506
  134. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/package.json +0 -140
  135. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/path.d.ts +0 -200
  136. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/perf_hooks.d.ts +0 -961
  137. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/process.d.ts +0 -1961
  138. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/punycode.d.ts +0 -117
  139. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/querystring.d.ts +0 -152
  140. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/readline.d.ts +0 -589
  141. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/repl.d.ts +0 -430
  142. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/sea.d.ts +0 -153
  143. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/stream.d.ts +0 -1698
  144. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/string_decoder.d.ts +0 -67
  145. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/test.d.ts +0 -1787
  146. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/timers.d.ts +0 -286
  147. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/tls.d.ts +0 -1259
  148. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/trace_events.d.ts +0 -197
  149. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/tty.d.ts +0 -208
  150. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/url.d.ts +0 -964
  151. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/util.d.ts +0 -2331
  152. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/v8.d.ts +0 -809
  153. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/vm.d.ts +0 -1001
  154. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/wasi.d.ts +0 -181
  155. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/worker_threads.d.ts +0 -715
  156. package/dist/node_modules/@struct-ai/sdk/node_modules/@types/node/zlib.d.ts +0 -598
  157. package/dist/node_modules/@struct-ai/sdk/node_modules/nock/package.json +0 -89
  158. package/dist/node_modules/@struct-ai/sdk/node_modules/tshy/package.json +0 -81
  159. package/dist/node_modules/@struct-ai/sdk/node_modules/typescript/package.json +0 -120
  160. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/config.d.ts +0 -3
  161. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/coverage.d.ts +0 -1
  162. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/environments.d.ts +0 -1
  163. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/globals.d.ts +0 -22
  164. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/import-meta.d.ts +0 -5
  165. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/importMeta.d.ts +0 -4
  166. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/jsdom.d.ts +0 -6
  167. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/mocker.d.ts +0 -1
  168. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/node.d.ts +0 -1
  169. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/optional-runtime-types.d.ts +0 -6
  170. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/optional-types.d.ts +0 -7
  171. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/package.json +0 -239
  172. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/reporters.d.ts +0 -1
  173. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/runners.d.ts +0 -1
  174. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/snapshot.d.ts +0 -1
  175. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/suite.d.ts +0 -1
  176. package/dist/node_modules/@struct-ai/sdk/node_modules/vitest/worker.d.ts +0 -1
  177. package/dist/node_modules/@struct-ai/sdk/package.json +0 -100
package/README.md CHANGED
@@ -60,7 +60,7 @@ await struct.agent({ name: "checkout" }, async () => {
60
60
  | `@langchain/core` `BaseChatModel` | `.invoke`, `.stream` | `chat {model}` | Skipped when a provider-direct instrumentor is active (e.g. ChatAnthropic + Anthropic patch → single span) |
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
- | `@langchain/langgraph` `Pregel` | `.invoke`, `.stream` | `invoke_agent {name}` | Covers `createReactAgent`, custom graphs. `thread_id` → `gen_ai.conversation.id` |
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)`. |
64
64
 
65
65
  ## Framework integration
66
66
 
@@ -97,6 +97,39 @@ import { createReactAgent } from "@langchain/langgraph/prebuilt";
97
97
  // BaseRetriever.invoke gets retrieval spans.
98
98
  ```
99
99
 
100
+ ##### Recommended pattern: wrap LangChain entry points in `struct.agent`
101
+
102
+ For multi-turn HTTP-style usage (every request continues the same
103
+ conversation), wrap your request handler in
104
+ `struct.agent({ sessionId: conversationId }, async () => { ... })`.
105
+ This is the struct-native replacement for `with ls.tracing_context(parent=run_tree):`
106
+ and gives you two things you can't get from `configurable.thread_id` alone:
107
+
108
+ 1. **Threading without per-call config plumbing.** Every nested
109
+ LangChain call inherits the conversation id via the SDK's ambient
110
+ AsyncLocalStorage — you don't have to ensure each `compiledGraph.invoke`
111
+ gets `thread_id` on its config.
112
+ 2. **One trace per request.** `struct.agent` creates a parent OTel span
113
+ so all the LangChain work for the request nests under one trace
114
+ (clean tree, "Subagents" / "Spawned by" UI links work). Without it,
115
+ each `.invoke()` becomes its own root trace, and the UI's session
116
+ list shows a non-deterministic agent name (`omni_agent`,
117
+ `LangGraph`, the first sub-agent it sees…).
118
+
119
+ Migrating from LangSmith:
120
+
121
+ ```ts
122
+ // Before — LangSmith convention, fragments under struct-sdk
123
+ await ls.traceable(async () => {
124
+ await orchestrator.invoke(inputs, config);
125
+ }, { parent: runTree })();
126
+
127
+ // After — struct-native, threads correctly, no langsmith dep
128
+ await struct.agent({ sessionId: conversationId }, async () => {
129
+ await orchestrator.invoke(inputs, config);
130
+ });
131
+ ```
132
+
100
133
  ### LLM SDKs used directly — manual agent + tool scopes required
101
134
 
102
135
  When you call an LLM SDK directly (no agent framework wrapping it), only
@@ -201,8 +234,18 @@ await struct.agent(
201
234
  );
202
235
  ```
203
236
 
204
- Nested agents set `struct.agent.parent_session_id` on the inner span, linking
205
- subagents back to the parent.
237
+ Sub-agents (e.g. a `createReactAgent` graph invoked from inside another
238
+ agent's tool body) record their parent via the
239
+ `struct.agent.parent_session_id` attribute on the inner `invoke_agent`
240
+ span. This powers the UI's "Spawned by" backlink, which works for any
241
+ nested invocation.
242
+
243
+ The parent's "Subagents" forward list — the inverse direction — requires
244
+ that the nested invoke inherits the parent's trace via LangChain's
245
+ callback chain. This works automatically when the outer tool is built
246
+ with the `tool()` factory from `@langchain/core/tools`; it does **not**
247
+ work with `new DynamicTool({...})`, which skips the callback wrap. See
248
+ the troubleshooting section below for the workaround.
206
249
 
207
250
  ## Semantic conventions
208
251
 
@@ -302,7 +302,14 @@ class StructSDK {
302
302
  // gen_ai.conversation.id is the spec-blessed name for
303
303
  // session/thread id.
304
304
  startedSpan.setAttribute(semconv_js_1.GEN_AI.CONVERSATION_ID, sessionId);
305
- if (parentSessionId && parentSessionId !== sessionId) {
305
+ // Always set parent_session_id when there's a parent agent — even
306
+ // when the value matches sessionId (which happens when a nested
307
+ // ``struct.agent()`` inherits ambient session). The attribute is
308
+ // structural ("this agent has a parent agent"), not a uniqueness
309
+ // marker. The UI uses it to render an inline subagent expansion
310
+ // under the triggering call (vs the drill-in flow used when
311
+ // sessionIds differ).
312
+ if (parentSessionId) {
306
313
  startedSpan.setAttribute(semconv_js_1.STRUCT.AGENT_PARENT_SESSION_ID, parentSessionId);
307
314
  }
308
315
  if (options.metadata) {
@@ -164,7 +164,16 @@ class StructCallbackHandler {
164
164
  // agent ancestor (NOT the immediate parent run) — a subagent spawned
165
165
  // from inside a tool has the tool as its immediate parent, but we want
166
166
  // to point at the outer agent's session.
167
- if (parentAgentSessionId && parentAgentSessionId !== sessionId) {
167
+ //
168
+ // Always set when there's a parent agent — even if the value matches
169
+ // sessionId. When the SDK's ambient session (e.g. ``struct.agent({
170
+ // sessionId: convId })``) propagates through nested invoke_agent
171
+ // spans, parent and child share sessionId. The attribute still
172
+ // encodes the structural "this span has a parent agent"
173
+ // relationship; the UI uses it to render an inline subagent
174
+ // expansion under the triggering tool call (vs the "View sub-agent →"
175
+ // drill-in flow used when sessionIds differ).
176
+ if (parentAgentSessionId) {
168
177
  startedSpan.setAttribute(semconv_js_1.STRUCT.AGENT_PARENT_SESSION_ID, parentAgentSessionId);
169
178
  }
170
179
  // User prompt propagation — set gen_ai.input.messages on the agent
@@ -461,8 +470,8 @@ class StructCallbackHandler {
461
470
  if (p?.sessionId)
462
471
  return p.sessionId;
463
472
  }
464
- const threadId = metadata?.thread_id;
465
- if (typeof threadId === "string" && threadId.length > 0)
473
+ const threadId = metadataThreadId(metadata);
474
+ if (threadId)
466
475
  return threadId;
467
476
  const ambient = (0, context_js_1.getSessionId)();
468
477
  if (ambient)
@@ -498,8 +507,8 @@ class StructCallbackHandler {
498
507
  * resolved session and ignore the inherited value.
499
508
  */
500
509
  resolveAgentSessionId(metadata, parentSessionId) {
501
- const threadId = metadata?.thread_id;
502
- if (typeof threadId === "string" && threadId.length > 0) {
510
+ const threadId = metadataThreadId(metadata);
511
+ if (threadId) {
503
512
  if (parentSessionId && threadId === parentSessionId) {
504
513
  // Inherited from parent — treat as unset, assign fresh.
505
514
  return (0, node_crypto_1.randomUUID)();
@@ -545,6 +554,32 @@ const AGENT_CLASSES = new Set([
545
554
  "Pregel",
546
555
  "LangGraph",
547
556
  ]);
557
+ /**
558
+ * Threading-id metadata keys, in resolution order.
559
+ *
560
+ * ``thread_id`` is LangGraph's canonical name (checkpointer key); ``session_id``
561
+ * and ``conversation_id`` are the LangSmith conventions documented at
562
+ * https://docs.langchain.com/langsmith/threads — when users tag a run with any
563
+ * of these, we treat it as the conversation grouping key. This makes
564
+ * struct-sdk drop-in compatible for users following either naming.
565
+ */
566
+ const THREAD_KEYS = ["thread_id", "session_id", "conversation_id"];
567
+ /**
568
+ * Pull the conversation/thread id from a LangChain ``metadata`` dict.
569
+ * Returns ``undefined`` if no recognised key is present as a non-empty
570
+ * string, so callers chain to their own fallbacks (ambient session,
571
+ * fresh UUID).
572
+ */
573
+ function metadataThreadId(metadata) {
574
+ if (!metadata)
575
+ return undefined;
576
+ for (const key of THREAD_KEYS) {
577
+ const v = metadata[key];
578
+ if (typeof v === "string" && v.length > 0)
579
+ return v;
580
+ }
581
+ return undefined;
582
+ }
548
583
  function isAgentChain(chain, runType, _runName) {
549
584
  if (runType === "agent")
550
585
  return true;
package/dist/esm/core.js CHANGED
@@ -298,7 +298,14 @@ export class StructSDK {
298
298
  // gen_ai.conversation.id is the spec-blessed name for
299
299
  // session/thread id.
300
300
  startedSpan.setAttribute(GEN_AI.CONVERSATION_ID, sessionId);
301
- if (parentSessionId && parentSessionId !== sessionId) {
301
+ // Always set parent_session_id when there's a parent agent — even
302
+ // when the value matches sessionId (which happens when a nested
303
+ // ``struct.agent()`` inherits ambient session). The attribute is
304
+ // structural ("this agent has a parent agent"), not a uniqueness
305
+ // marker. The UI uses it to render an inline subagent expansion
306
+ // under the triggering call (vs the drill-in flow used when
307
+ // sessionIds differ).
308
+ if (parentSessionId) {
302
309
  startedSpan.setAttribute(STRUCT.AGENT_PARENT_SESSION_ID, parentSessionId);
303
310
  }
304
311
  if (options.metadata) {
@@ -161,7 +161,16 @@ export class StructCallbackHandler {
161
161
  // agent ancestor (NOT the immediate parent run) — a subagent spawned
162
162
  // from inside a tool has the tool as its immediate parent, but we want
163
163
  // to point at the outer agent's session.
164
- if (parentAgentSessionId && parentAgentSessionId !== sessionId) {
164
+ //
165
+ // Always set when there's a parent agent — even if the value matches
166
+ // sessionId. When the SDK's ambient session (e.g. ``struct.agent({
167
+ // sessionId: convId })``) propagates through nested invoke_agent
168
+ // spans, parent and child share sessionId. The attribute still
169
+ // encodes the structural "this span has a parent agent"
170
+ // relationship; the UI uses it to render an inline subagent
171
+ // expansion under the triggering tool call (vs the "View sub-agent →"
172
+ // drill-in flow used when sessionIds differ).
173
+ if (parentAgentSessionId) {
165
174
  startedSpan.setAttribute(STRUCT.AGENT_PARENT_SESSION_ID, parentAgentSessionId);
166
175
  }
167
176
  // User prompt propagation — set gen_ai.input.messages on the agent
@@ -458,8 +467,8 @@ export class StructCallbackHandler {
458
467
  if (p?.sessionId)
459
468
  return p.sessionId;
460
469
  }
461
- const threadId = metadata?.thread_id;
462
- if (typeof threadId === "string" && threadId.length > 0)
470
+ const threadId = metadataThreadId(metadata);
471
+ if (threadId)
463
472
  return threadId;
464
473
  const ambient = getSessionId();
465
474
  if (ambient)
@@ -495,8 +504,8 @@ export class StructCallbackHandler {
495
504
  * resolved session and ignore the inherited value.
496
505
  */
497
506
  resolveAgentSessionId(metadata, parentSessionId) {
498
- const threadId = metadata?.thread_id;
499
- if (typeof threadId === "string" && threadId.length > 0) {
507
+ const threadId = metadataThreadId(metadata);
508
+ if (threadId) {
500
509
  if (parentSessionId && threadId === parentSessionId) {
501
510
  // Inherited from parent — treat as unset, assign fresh.
502
511
  return randomUUID();
@@ -541,6 +550,32 @@ const AGENT_CLASSES = new Set([
541
550
  "Pregel",
542
551
  "LangGraph",
543
552
  ]);
553
+ /**
554
+ * Threading-id metadata keys, in resolution order.
555
+ *
556
+ * ``thread_id`` is LangGraph's canonical name (checkpointer key); ``session_id``
557
+ * and ``conversation_id`` are the LangSmith conventions documented at
558
+ * https://docs.langchain.com/langsmith/threads — when users tag a run with any
559
+ * of these, we treat it as the conversation grouping key. This makes
560
+ * struct-sdk drop-in compatible for users following either naming.
561
+ */
562
+ const THREAD_KEYS = ["thread_id", "session_id", "conversation_id"];
563
+ /**
564
+ * Pull the conversation/thread id from a LangChain ``metadata`` dict.
565
+ * Returns ``undefined`` if no recognised key is present as a non-empty
566
+ * string, so callers chain to their own fallbacks (ambient session,
567
+ * fresh UUID).
568
+ */
569
+ function metadataThreadId(metadata) {
570
+ if (!metadata)
571
+ return undefined;
572
+ for (const key of THREAD_KEYS) {
573
+ const v = metadata[key];
574
+ if (typeof v === "string" && v.length > 0)
575
+ return v;
576
+ }
577
+ return undefined;
578
+ }
544
579
  function isAgentChain(chain, runType, _runName) {
545
580
  if (runType === "agent")
546
581
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@struct-ai/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "description": "Struct agent observability SDK — auto-instruments AI agent frameworks with OpenTelemetry",
5
5
  "type": "module",
6
6
  "main": "./dist/commonjs/index.js",
@@ -24,6 +24,15 @@
24
24
  "README.md",
25
25
  "LICENSE"
26
26
  ],
27
+ "scripts": {
28
+ "build": "tshy",
29
+ "dev": "tshy --watch",
30
+ "clean": "rm -rf dist .tshy .tshy-build",
31
+ "typecheck": "tsc --noEmit",
32
+ "lint": "echo 'TODO: eslint not configured for @struct-ai/sdk yet — see investigation'",
33
+ "test": "vitest run",
34
+ "test:watch": "vitest"
35
+ },
27
36
  "keywords": [
28
37
  "struct",
29
38
  "observability",
@@ -76,25 +85,19 @@
76
85
  "@langchain/anthropic": "^0.3.0",
77
86
  "@langchain/core": "^0.3.0",
78
87
  "@langchain/langgraph": "^0.2.0",
88
+ "@langchain/openai": "^0.5.18",
79
89
  "@types/node": "^20.0.0",
80
90
  "nock": "^13.5.0",
81
91
  "tshy": "^3.0.0",
92
+ "tsx": "^4.21.0",
82
93
  "typescript": "^5.5.0",
83
- "vitest": "^4.0.0"
94
+ "vitest": "^4.0.0",
95
+ "zod": "^4.4.3"
84
96
  },
85
97
  "tshy": {
86
98
  "exports": {
87
99
  ".": "./src/index.ts"
88
100
  }
89
101
  },
90
- "module": "./dist/esm/index.js",
91
- "scripts": {
92
- "build": "tshy",
93
- "dev": "tshy --watch",
94
- "clean": "rm -rf dist .tshy .tshy-build",
95
- "typecheck": "tsc --noEmit",
96
- "lint": "eslint src --max-warnings 0",
97
- "test": "vitest run",
98
- "test:watch": "vitest"
99
- }
100
- }
102
+ "module": "./dist/esm/index.js"
103
+ }
@@ -1,10 +0,0 @@
1
- export declare enum ContentCaptureMode {
2
- None = "none",
3
- EventOnly = "event_only",
4
- SpanOnly = "span_only",
5
- SpanAndEvent = "span_and_event"
6
- }
7
- export declare function emitEventsFor(mode: ContentCaptureMode): boolean;
8
- export declare function emitSpanContentFor(mode: ContentCaptureMode): boolean;
9
- export declare function captureContentFor(mode: ContentCaptureMode): boolean;
10
- //# sourceMappingURL=content-capture.d.ts.map
@@ -1,30 +0,0 @@
1
- import type { Span } from "@opentelemetry/api";
2
- export interface StructContext {
3
- sessionId?: string;
4
- conversationId?: string;
5
- agentSpan?: Span;
6
- pendingToolCalls?: Record<string, string[]>;
7
- }
8
- export declare function getStore(): StructContext | undefined;
9
- export declare function getSessionId(): string | undefined;
10
- export declare function getConversationId(): string | undefined;
11
- export declare function getAgentSpan(): Span | undefined;
12
- export declare function getPendingToolCalls(): Record<string, string[]> | undefined;
13
- export declare function runWithContext<T>(patch: Partial<StructContext>, fn: () => T): T;
14
- export declare function ensurePendingToolCallsSlot(): Record<string, string[]>;
15
- /**
16
- * Pop the first pending tool_use id matching `name`, or undefined.
17
- * FIFO semantics match Python struct-sdk-python.
18
- */
19
- export declare function popPendingToolCallId(name: string): string | undefined;
20
- /**
21
- * Push (name, id) pairs into the active pending-tool-calls slot.
22
- * Initializes a transient dict if no active agent scope.
23
- */
24
- export declare function pushPendingToolCalls(pairs: readonly [string, string][]): void;
25
- /** Snapshot the store for wrapping async iterators. */
26
- export declare function snapshotStore(): StructContext | undefined;
27
- export declare function runWithStore<T>(store: StructContext | undefined, fn: () => T): T;
28
- /** Test-only: run `fn` inside a completely fresh context (no parent store). */
29
- export declare function runInFreshContext<T>(fn: () => T): T;
30
- //# sourceMappingURL=context.d.ts.map
@@ -1,120 +0,0 @@
1
- import { type Tracer } from "@opentelemetry/api";
2
- import { type Logger } from "@opentelemetry/api-logs";
3
- import { type LogRecordProcessor } from "@opentelemetry/sdk-logs";
4
- import type { SpanProcessor } from "@opentelemetry/sdk-trace-base";
5
- import { ContentCaptureMode } from "./content-capture.js";
6
- export declare const DEFAULT_ENDPOINT = "https://ingest.struct.ai";
7
- export interface InitOptions {
8
- ingestKey: string;
9
- serviceName?: string;
10
- serviceVersion?: string;
11
- environment?: string;
12
- endpoint?: string;
13
- captureContent?: boolean;
14
- contentCapture?: ContentCaptureMode;
15
- /**
16
- * Maximum time `shutdown()` is allowed to spend flushing telemetry providers
17
- * before returning. If the ingest endpoint is dead or slow, shutdown returns
18
- * within this budget rather than hanging the host process exit. Defaults to
19
- * 5000ms. Can be overridden per call via `shutdown(timeoutMs)`.
20
- */
21
- shutdownTimeoutMs?: number;
22
- }
23
- export interface AgentScopeOptions {
24
- name: string;
25
- sessionId?: string;
26
- /**
27
- * Stable identifier for the agent DEFINITION — `gen_ai.agent.id` per the OTel
28
- * GenAI spec. Not the same as sessionId (which identifies a single
29
- * invocation / conversation). Omit if you don't have a stable ID.
30
- */
31
- agentId?: string;
32
- version?: string;
33
- metadata?: Record<string, string>;
34
- }
35
- export interface ToolScopeOptions {
36
- name: string;
37
- toolCallId?: string;
38
- }
39
- export interface InternalLogger {
40
- info(msg: string, ...args: unknown[]): void;
41
- warn(msg: string, ...args: unknown[]): void;
42
- debug(msg: string, ...args: unknown[]): void;
43
- }
44
- /**
45
- * Sites that have already logged a first-failure WARN; subsequent failures at
46
- * the same site log at DEBUG. Mirrors `_first_failure_logged` in the Python
47
- * SDK.
48
- *
49
- * Exported for tests that need to clear the set between runs so log-level
50
- * assertions are deterministic. Internal — not re-exported from `index.ts`,
51
- * so consumers cannot reach it via the package's public API surface.
52
- */
53
- export declare const firstFailureLogged: Set<string>;
54
- /**
55
- * Run `fn`; swallow any exception. The first failure per `site` logs at WARN
56
- * (with the error attached); subsequent failures at the same site log at
57
- * DEBUG. The caller's logger is passed explicitly so this helper stays a free
58
- * module-level function — usable from instrumentation modules that don't
59
- * carry an SDK reference of their own.
60
- *
61
- * Mirrors `_safe(fn, *, site)` in the Python SDK.
62
- */
63
- export declare function safe(fn: () => void, site: string, logger: InternalLogger): void;
64
- export declare class StructSDK {
65
- private _initialized;
66
- private _tracerProvider;
67
- private _loggerProvider;
68
- private _ingestKey;
69
- private _endpoint;
70
- private _contentCapture;
71
- private _shutdownHook;
72
- private _shutdownTimeoutMs;
73
- private _internalLogger;
74
- private _readyPromise;
75
- /** Resolves once auto-instrumentation has attempted to patch every detected library. */
76
- get ready(): Promise<void>;
77
- get initialized(): boolean;
78
- get endpoint(): string;
79
- get ingestKey(): string;
80
- get contentCapture(): ContentCaptureMode;
81
- get captureContent(): boolean;
82
- get emitEvents(): boolean;
83
- get emitSpanContent(): boolean;
84
- init(options: InitOptions): void;
85
- getTracer(name?: string): Tracer;
86
- getLogger(name?: string): Logger;
87
- getInternalLogger(): InternalLogger;
88
- /** Test-only: attach a custom span processor to the internal provider. */
89
- addSpanProcessor(processor: SpanProcessor): void;
90
- /** Test-only: attach a custom log record processor. */
91
- addLogRecordProcessor(processor: LogRecordProcessor): void;
92
- /**
93
- * Shut down the SDK and flush pending telemetry.
94
- *
95
- * Returns within `timeoutMs` (default: SDK init's `shutdownTimeoutMs`,
96
- * fallback 5000ms). If the ingest endpoint is unreachable, in-flight
97
- * telemetry may be dropped — the trade-off vs. blocking process exit.
98
- *
99
- * In serverless / edge runtimes (Lambda, Vercel, etc.), the runtime does
100
- * NOT await our `beforeExit` hook. Call `await struct.shutdown()`
101
- * explicitly before your function returns or before container freeze.
102
- *
103
- * Safe to call multiple times. Safe to call on an SDK that failed to init
104
- * or was never initialized.
105
- *
106
- * @param timeoutMs - Override the configured shutdown timeout for this call.
107
- */
108
- shutdown(timeoutMs?: number): Promise<void>;
109
- /**
110
- * Run `fn` inside an `invoke_agent` span scope.
111
- * Matches Python's _AgentContext behavior.
112
- */
113
- agent<T>(options: AgentScopeOptions, fn: () => Promise<T> | T): Promise<T>;
114
- /**
115
- * Run `fn` inside an `execute_tool` span scope.
116
- * Matches Python's _ToolContext behavior.
117
- */
118
- tool<T>(options: ToolScopeOptions, fn: () => Promise<T> | T): Promise<T>;
119
- }
120
- //# sourceMappingURL=core.d.ts.map
@@ -1,12 +0,0 @@
1
- import { type Logger } from "@opentelemetry/api-logs";
2
- /**
3
- * Emit per-message log events for an Anthropic messages.create() call.
4
- * Port of _emit_message_events from anthropic.py.
5
- */
6
- export declare function emitAnthropicMessageEvents(logger: Logger, messages: unknown, system: unknown): void;
7
- /**
8
- * Emit a gen_ai.choice LogRecord for an Anthropic response.
9
- * Port of _emit_choice_event from anthropic.py.
10
- */
11
- export declare function emitAnthropicChoiceEvent(logger: Logger, contentBlocks: unknown, stopReason: string | null | undefined): void;
12
- //# sourceMappingURL=events.d.ts.map
@@ -1,6 +0,0 @@
1
- import { StructSDK } from "./core.js";
2
- export { ContentCaptureMode } from "./content-capture.js";
3
- export type { AgentScopeOptions, InitOptions, InternalLogger, ToolScopeOptions, } from "./core.js";
4
- export { StructSDK } from "./core.js";
5
- export declare const struct: StructSDK;
6
- //# sourceMappingURL=index.d.ts.map
@@ -1,24 +0,0 @@
1
- import { truncateField } from "../truncation.js";
2
- /** Part in the GenAI spec format. */
3
- export type Part = Record<string, unknown>;
4
- /**
5
- * Walk Anthropic response content blocks, return (name, id) pairs for tool_use blocks.
6
- * Port of _iter_tool_uses from anthropic.py.
7
- */
8
- export declare function iterToolUses(blocks: unknown): Array<[string, string]>;
9
- /**
10
- * Convert arbitrary content (string | block[] | unknown) → GenAI parts.
11
- * Port of _content_to_parts from anthropic.py.
12
- */
13
- export declare function contentToParts(content: unknown): Part[];
14
- /** Convert Anthropic message list → GenAI input-messages JSON string. */
15
- export declare function toInputMessages(messages: unknown): string;
16
- /** Convert response content blocks → GenAI output-messages JSON string. */
17
- export declare function toOutputMessages(contentBlocks: unknown, stopReason: string | null | undefined): string;
18
- /** Convert system prompt (string | block[]) → GenAI system_instructions JSON string. */
19
- export declare function toSystemInstructions(system: unknown): string;
20
- export declare function safeJsonForTool(obj: unknown): string;
21
- /** Extract the last user message's parts for parent-span propagation. */
22
- export declare function lastUserMessageParts(messages: unknown): Part[] | undefined;
23
- export { truncateField };
24
- //# sourceMappingURL=anthropic-content.d.ts.map
@@ -1,34 +0,0 @@
1
- import { type Tracer } from "@opentelemetry/api";
2
- import type { Logger } from "@opentelemetry/api-logs";
3
- import { type StructSDK } from "../core.js";
4
- interface PatchContext {
5
- tracer: Tracer;
6
- sdk: StructSDK;
7
- logger: Logger | undefined;
8
- }
9
- interface CreateParams {
10
- model?: string;
11
- max_tokens?: number;
12
- temperature?: number;
13
- top_p?: number;
14
- top_k?: number;
15
- stop_sequences?: string[];
16
- messages?: unknown[];
17
- system?: unknown;
18
- tools?: unknown[];
19
- stream?: boolean;
20
- }
21
- export declare function patch(sdk: StructSDK): Promise<void>;
22
- export declare function unpatch(): Promise<void>;
23
- type CreateMethod = (this: unknown, params: CreateParams, opts?: unknown) => unknown;
24
- type StreamMethod = (this: unknown, params: CreateParams, opts?: unknown) => unknown;
25
- /** @internal */
26
- export type _PatchContextForTest = PatchContext;
27
- /** @internal */
28
- export declare function _wrapCreateForTest(original: CreateMethod): CreateMethod;
29
- /** @internal */
30
- export declare function _wrapStreamForTest(original: StreamMethod): StreamMethod;
31
- /** @internal */
32
- export declare function _setActivePatchCtxForTest(ctx: PatchContext | undefined): void;
33
- export {};
34
- //# sourceMappingURL=anthropic.d.ts.map
@@ -1,5 +0,0 @@
1
- import type { StructSDK } from "../core.js";
2
- export declare const patchedIntegrations: Set<string>;
3
- export declare function resetPatchedIntegrations(): void;
4
- export declare function autoInstrument(sdk: StructSDK): Promise<void>;
5
- //# sourceMappingURL=index.d.ts.map