@cognipeer/agent-sdk 0.9.2 → 0.9.4

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 CHANGED
@@ -417,6 +417,8 @@ You can disable payload capture with `logData: false` to keep only metrics, or c
417
417
 
418
418
  Each session/event also carries OTel-compatible correlation identifiers (`traceId`, `rootSpanId`, `spanId`, `parentSpanId`) so you can stitch agent traces into distributed telemetry pipelines.
419
419
 
420
+ Pass `tracing: { metadata: { ... } }` to attach arbitrary key-value tags (e.g. `{ complexity: "complex" }`) to every payload sent to the sink. The SDK does not interpret these tags itself — they're forwarded as-is so a downstream consumer (e.g. Console) can use them as reporting/attribution dimensions.
421
+
420
422
  ## Development
421
423
 
422
424
  Install dependencies and build the package:
package/dist/index.cjs CHANGED
@@ -294,63 +294,16 @@ var NativeJsonSchemaStrategy = class {
294
294
  }
295
295
  return resolvedSchema;
296
296
  }
297
- normalizeOpenAIStrictSchema(schema) {
298
- const visit = (node) => {
299
- if (!node || typeof node !== "object") return node;
300
- if (Array.isArray(node)) {
301
- return node.map(visit);
302
- }
303
- const clone2 = { ...node };
304
- if (clone2.properties && typeof clone2.properties === "object") {
305
- const normalizedProperties = {};
306
- for (const [key, value] of Object.entries(clone2.properties)) {
307
- normalizedProperties[key] = visit(value);
308
- }
309
- clone2.properties = normalizedProperties;
310
- clone2.required = Object.keys(normalizedProperties);
311
- if (clone2.additionalProperties === void 0) {
312
- clone2.additionalProperties = false;
313
- }
314
- }
315
- if (clone2.items) {
316
- clone2.items = visit(clone2.items);
317
- }
318
- if (clone2.definitions && typeof clone2.definitions === "object") {
319
- const normalizedDefinitions = {};
320
- for (const [key, value] of Object.entries(clone2.definitions)) {
321
- normalizedDefinitions[key] = visit(value);
322
- }
323
- clone2.definitions = normalizedDefinitions;
324
- }
325
- if (clone2.$defs && typeof clone2.$defs === "object") {
326
- const normalizedDefs = {};
327
- for (const [key, value] of Object.entries(clone2.$defs)) {
328
- normalizedDefs[key] = visit(value);
329
- }
330
- clone2.$defs = normalizedDefs;
331
- }
332
- for (const compositeKey of ["anyOf", "oneOf", "allOf"]) {
333
- if (Array.isArray(clone2[compositeKey])) {
334
- clone2[compositeKey] = clone2[compositeKey].map(visit);
335
- }
336
- }
337
- return clone2;
338
- };
339
- return visit(schema);
340
- }
341
297
  /**
342
298
  * Convert a Zod schema to the JSON Schema object used in response_format.
343
299
  */
344
300
  toJsonSchema(schema, name = "structured_response") {
345
301
  const result = zodToJsonSchema.zodToJsonSchema(schema, {
346
302
  name,
347
- openaiStrictMode: true,
348
- nameStrategy: "duplicate-ref",
349
- $refStrategy: "extract-to-root",
350
- nullableStrategy: "property"
303
+ target: "openAi",
304
+ $refStrategy: "root"
351
305
  });
352
- const normalized = this.normalizeOpenAIStrictSchema(result);
353
- return this.unwrapRootObjectSchema(normalized);
306
+ return this.unwrapRootObjectSchema(result);
354
307
  }
355
308
  /**
356
309
  * Build response_format configuration for the model call.
@@ -1209,9 +1162,9 @@ async function postStreamingSessionStart(baseUrl, headers, payload) {
1209
1162
  const url = `${baseUrl.replace(/\/$/, "")}/stream/${payload.sessionId}/start`;
1210
1163
  await postJsonReliable(url, headers, payload, { retry: true });
1211
1164
  }
1212
- async function postStreamingEvent(baseUrl, headers, sessionId, event) {
1165
+ async function postStreamingEvent(baseUrl, headers, sessionId, event, metadata) {
1213
1166
  const url = `${baseUrl.replace(/\/$/, "")}/stream/${sessionId}/events`;
1214
- await postJsonReliable(url, headers, { event }, { retry: false });
1167
+ await postJsonReliable(url, headers, { event, metadata }, { retry: false });
1215
1168
  }
1216
1169
  async function postStreamingSessionEnd(baseUrl, headers, payload) {
1217
1170
  const url = `${baseUrl.replace(/\/$/, "")}/stream/${payload.sessionId}/end`;
@@ -1261,6 +1214,8 @@ function eventToOtlpSpan(event) {
1261
1214
  stringAttr("cognipeer.actor.name", event.actor?.name),
1262
1215
  stringAttr("cognipeer.actor.role", event.actor?.role),
1263
1216
  stringAttr("cognipeer.model", event.model),
1217
+ intAttr("cognipeer.tokens.reasoning", event.reasoningTokens),
1218
+ stringAttr("cognipeer.finish_reason", event.finishReason),
1264
1219
  stringAttr("cognipeer.provider", event.provider),
1265
1220
  stringAttr("cognipeer.tool.details", event.toolDetails ? JSON.stringify(event.toolDetails) : void 0),
1266
1221
  intAttr("cognipeer.tokens.input", event.inputTokens),
@@ -1316,7 +1271,8 @@ function traceSessionToOtlp(session) {
1316
1271
  stringAttr("cognipeer.session.id", session.sessionId),
1317
1272
  stringAttr("cognipeer.thread.id", session.threadId),
1318
1273
  stringAttr("cognipeer.agent.model", session.agent?.model),
1319
- stringAttr("cognipeer.agent.provider", session.agent?.provider)
1274
+ stringAttr("cognipeer.agent.provider", session.agent?.provider),
1275
+ ...Object.entries(session.metadata || {}).map(([key, value]) => stringAttr(`cognipeer.metadata.${key}`, value))
1320
1276
  ].filter(Boolean);
1321
1277
  const rootAttrs = [
1322
1278
  stringAttr("cognipeer.session.status", session.status),
@@ -1488,6 +1444,8 @@ function defaultSectionLabel(kind) {
1488
1444
  return "Details";
1489
1445
  case "tool_definitions":
1490
1446
  return "Tool Definitions";
1447
+ case "response_format":
1448
+ return "Response Format";
1491
1449
  default:
1492
1450
  return "Section";
1493
1451
  }
@@ -1545,6 +1503,42 @@ function buildToolDefinitionsSection(toolDefinitions) {
1545
1503
  }
1546
1504
  return section;
1547
1505
  }
1506
+ var RESPONSE_FORMAT_MAX_SECTION_BYTES = 64 * 1024;
1507
+ var RESPONSE_FORMAT_MAX_NAME_CHARS = 200;
1508
+ function buildResponseFormatSection(responseFormat, strategy) {
1509
+ if (!responseFormat || typeof responseFormat !== "object" || Array.isArray(responseFormat)) return void 0;
1510
+ const raw = responseFormat.response_format ?? responseFormat.responseFormat ?? responseFormat;
1511
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
1512
+ const type = typeof raw.type === "string" && raw.type.trim().length > 0 ? raw.type.trim() : void 0;
1513
+ const jsonSchema = raw.json_schema ?? raw.jsonSchema;
1514
+ const schemaSource = jsonSchema && typeof jsonSchema === "object" ? jsonSchema : raw;
1515
+ const schema = schemaSource.schema && typeof schemaSource.schema === "object" && !Array.isArray(schemaSource.schema) ? schemaSource.schema : void 0;
1516
+ if (!type && !schema) return void 0;
1517
+ const section = {
1518
+ kind: "response_format",
1519
+ label: "Response Format",
1520
+ type: type ?? (schema ? "json_schema" : "unknown"),
1521
+ ...strategy ? { strategy } : {},
1522
+ ...typeof schemaSource.name === "string" && schemaSource.name.length > 0 ? { schemaName: schemaSource.name.slice(0, RESPONSE_FORMAT_MAX_NAME_CHARS) } : {},
1523
+ ...typeof schemaSource.strict === "boolean" ? { strict: schemaSource.strict } : {}
1524
+ };
1525
+ if (schema) {
1526
+ try {
1527
+ section.schema = JSON.parse(JSON.stringify(schema));
1528
+ } catch {
1529
+ }
1530
+ }
1531
+ try {
1532
+ if (section.schema && buffer.Buffer.byteLength(JSON.stringify(section), "utf8") > RESPONSE_FORMAT_MAX_SECTION_BYTES) {
1533
+ delete section.schema;
1534
+ section.truncated = true;
1535
+ }
1536
+ } catch {
1537
+ delete section.schema;
1538
+ section.truncated = true;
1539
+ }
1540
+ return section;
1541
+ }
1548
1542
  function buildMessageLabel(role, index) {
1549
1543
  const normalized = role?.trim().length ? role.trim() : "message";
1550
1544
  const base = `${normalized.charAt(0).toUpperCase()}${normalized.slice(1)} Message`;
@@ -1653,6 +1647,7 @@ function createTraceSession(opts) {
1653
1647
  rootSpanId,
1654
1648
  threadId: opts.tracing?.threadId,
1655
1649
  configAgentName: opts.tracing?.agentName?.trim() || void 0,
1650
+ configMetadata: opts.tracing?.metadata && Object.keys(opts.tracing.metadata).length > 0 ? opts.tracing.metadata : void 0,
1656
1651
  startedAt: Date.now(),
1657
1652
  resolvedConfig: cfg,
1658
1653
  events: [],
@@ -1691,6 +1686,7 @@ async function startStreamingSession(session, agentRuntime) {
1691
1686
  threadId: session.threadId,
1692
1687
  startedAt: startedAtIso,
1693
1688
  agent: agentInfo,
1689
+ metadata: session.configMetadata,
1694
1690
  config: configSnapshot
1695
1691
  };
1696
1692
  if (sink.type === "cognipeer" || sink.type === "http") {
@@ -1740,6 +1736,7 @@ function recordTraceEvent(session, event) {
1740
1736
  if (!Number.isNaN(cachedInputTokens ?? NaN) && cachedInputTokens !== void 0) {
1741
1737
  session.summary.totalCachedInputTokens += cachedInputTokens;
1742
1738
  }
1739
+ const reasoningTokens = event.reasoningTokens !== void 0 ? Number(event.reasoningTokens) : void 0;
1743
1740
  const totalTokens = event.totalTokens !== void 0 ? Number(event.totalTokens) : void 0;
1744
1741
  const requestBytes = event.requestBytes !== void 0 ? Number(event.requestBytes) : void 0;
1745
1742
  if (!Number.isNaN(requestBytes ?? NaN) && requestBytes !== void 0) {
@@ -1765,6 +1762,13 @@ function recordTraceEvent(session, event) {
1765
1762
  if (menuSection) sections = ensureUniqueSections(id, [...sections ?? [], menuSection]);
1766
1763
  }
1767
1764
  }
1765
+ if (event.responseFormat) {
1766
+ const existing = (sections ?? []).some((section) => section.kind === "response_format");
1767
+ if (!existing) {
1768
+ const formatSection = buildResponseFormatSection(event.responseFormat, event.responseFormatStrategy);
1769
+ if (formatSection) sections = ensureUniqueSections(id, [...sections ?? [], formatSection]);
1770
+ }
1771
+ }
1768
1772
  }
1769
1773
  const resolvedModel = event.model ?? inferModelFromMessages(event.messageList);
1770
1774
  const resolvedProvider = event.provider ?? inferProviderFromMessages(event.messageList);
@@ -1795,6 +1799,11 @@ function recordTraceEvent(session, event) {
1795
1799
  ...totalTokens !== void 0 && { totalTokens },
1796
1800
  ...cachedInputTokens !== void 0 && { cachedInputTokens }
1797
1801
  },
1802
+ // Absent, never zeroed for ai_call the way the four above are: a model
1803
+ // that does no reasoning and one whose provider reports nothing are
1804
+ // different facts, and a zero would erase the difference.
1805
+ ...reasoningTokens !== void 0 && !Number.isNaN(reasoningTokens) ? { reasoningTokens } : {},
1806
+ ...event.finishReason ? { finishReason: event.finishReason } : {},
1798
1807
  requestBytes,
1799
1808
  responseBytes,
1800
1809
  model: resolvedModel,
@@ -1838,7 +1847,7 @@ function recordTraceEvent(session, event) {
1838
1847
  if (session.resolvedConfig.mode === "streaming" && session.sessionStarted) {
1839
1848
  if (sink.type === "cognipeer" || sink.type === "http") {
1840
1849
  const headers = sink.type === "cognipeer" ? { Authorization: `Bearer ${sink.apiKey}` } : sink.headers;
1841
- postStreamingEvent(sink.url, headers, session.sessionId, record).catch((err) => {
1850
+ postStreamingEvent(sink.url, headers, session.sessionId, record, session.configMetadata).catch((err) => {
1842
1851
  const errMsg = err instanceof Error ? err.message : String(err);
1843
1852
  if (typeof console !== "undefined" && typeof console.warn === "function") {
1844
1853
  console.warn("[Tracing] Failed to send streaming event:", errMsg);
@@ -1890,6 +1899,7 @@ async function finalizeTraceSession(session, params = {}) {
1890
1899
  endedAt: endedAtIso,
1891
1900
  durationMs,
1892
1901
  agent: agentInfo || session.agentInfo,
1902
+ metadata: session.configMetadata,
1893
1903
  config: configSnapshot,
1894
1904
  summary: session.summary,
1895
1905
  events: session.events,
@@ -1924,7 +1934,8 @@ async function finalizeTraceSession(session, params = {}) {
1924
1934
  durationMs,
1925
1935
  status: initialStatus,
1926
1936
  summary: session.summary,
1927
- errors: session.errors
1937
+ errors: session.errors,
1938
+ metadata: session.configMetadata
1928
1939
  });
1929
1940
  } else {
1930
1941
  await postTraceSession(sink.url, headers, payloadForSink);
@@ -3892,6 +3903,18 @@ function createAgentCoreNode(opts) {
3892
3903
  }
3893
3904
  }
3894
3905
  const responseFormat = runtime.responseFormat;
3906
+ let traceResponseFormat = responseFormat;
3907
+ let traceResponseFormatStrategy = responseFormat ? "native" : void 0;
3908
+ if (!traceResponseFormat && runtime.outputSchema) {
3909
+ const responseTool = traceToolDefinitions?.find((tool) => tool.name === "response");
3910
+ if (responseTool?.parameters) {
3911
+ traceResponseFormat = {
3912
+ type: "json_schema",
3913
+ json_schema: { name: "response", strict: false, schema: responseTool.parameters }
3914
+ };
3915
+ traceResponseFormatStrategy = "tool_based";
3916
+ }
3917
+ }
3895
3918
  const reasoningForCall = state.ctx?.__reasoning;
3896
3919
  const reasoningInvokeOpts = reasoningForCall ? { reasoning: reasoningForCall } : void 0;
3897
3920
  const extractText2 = (chunk) => {
@@ -3951,7 +3974,9 @@ function createAgentCoreNode(opts) {
3951
3974
  provider: getProviderName(runtime.model || opts.model),
3952
3975
  error: { message: err?.message || String(err), stack: err?.stack },
3953
3976
  messageList: state.messages,
3954
- toolDefinitions: traceToolDefinitions
3977
+ toolDefinitions: traceToolDefinitions,
3978
+ responseFormat: traceResponseFormat,
3979
+ responseFormatStrategy: traceResponseFormatStrategy
3955
3980
  });
3956
3981
  throw err;
3957
3982
  }
@@ -3966,6 +3991,7 @@ function createAgentCoreNode(opts) {
3966
3991
  const shouldLogResponse = !!traceSession && traceSession.resolvedConfig.logData;
3967
3992
  const responsePayload = shouldLogResponse ? sanitizeTracePayload(response) : void 0;
3968
3993
  const responseBytes = responsePayload !== void 0 ? estimatePayloadBytes(responsePayload) : void 0;
3994
+ const finishReason = response?.response_metadata?.finish_reason ?? response?.response_metadata?.finishReason ?? response?.finishReason;
3969
3995
  recordTraceEvent(traceSession, {
3970
3996
  type: "ai_call",
3971
3997
  label: "Assistant Response",
@@ -3975,12 +4001,18 @@ function createAgentCoreNode(opts) {
3975
4001
  outputTokens: normalized?.completion_tokens,
3976
4002
  totalTokens: normalized?.total_tokens,
3977
4003
  cachedInputTokens: normalized?.prompt_tokens_details?.cached_tokens,
4004
+ // A subset of completion_tokens, and on a reasoning model routinely most
4005
+ // of the output bill while being invisible in the response text.
4006
+ reasoningTokens: normalized?.completion_tokens_details?.reasoning_tokens,
4007
+ finishReason: typeof finishReason === "string" ? finishReason : void 0,
3978
4008
  requestBytes: promptBytes,
3979
4009
  responseBytes,
3980
4010
  model: modelName,
3981
4011
  provider: getProviderName(runtime.model || opts.model),
3982
4012
  messageList: messagesWithResponse,
3983
- toolDefinitions: traceToolDefinitions
4013
+ toolDefinitions: traceToolDefinitions,
4014
+ responseFormat: traceResponseFormat,
4015
+ responseFormatStrategy: traceResponseFormatStrategy
3984
4016
  });
3985
4017
  if (normalized) {
3986
4018
  recordUsage(state, modelName, normalized);
@@ -11793,6 +11825,7 @@ exports.ToolBasedStrategy = ToolBasedStrategy;
11793
11825
  exports.VertexProvider = VertexProvider;
11794
11826
  exports.agentVerdictRule = agentVerdictRule;
11795
11827
  exports.appendBoundTools = appendBoundTools;
11828
+ exports.buildResponseFormatSection = buildResponseFormatSection;
11796
11829
  exports.buildSkillHeaderBlock = buildSkillHeaderBlock;
11797
11830
  exports.buildSubagentCatalogBlock = buildSubagentCatalogBlock;
11798
11831
  exports.buildSystemPrompt = buildSystemPrompt;