@synova-cloud/sdk 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -263,6 +263,116 @@ const topic = await client.prompts.execute('prm_abc123', {
263
263
  | `@SchemaMaxItems(n)` | Maximum array length |
264
264
  | `@SchemaEnum(values)` | Allowed enum values |
265
265
 
266
+ ### Observability
267
+
268
+ Track and group your LLM calls using traces and spans. Each execution creates a span, and multiple spans can be grouped into a trace using `sessionId`.
269
+
270
+ #### Session-Based Tracing
271
+
272
+ Use `sessionId` to group related calls (e.g., a conversation) into a single trace:
273
+
274
+ ```typescript
275
+ const sessionId = 'chat_user123_conv1';
276
+
277
+ // First message - creates new trace
278
+ const response1 = await client.prompts.execute('prm_abc123', {
279
+ provider: 'openai',
280
+ model: 'gpt-4o',
281
+ sessionId,
282
+ variables: { topic: 'TypeScript' },
283
+ });
284
+
285
+ console.log(response1.traceId); // trc_xxx
286
+ console.log(response1.spanId); // spn_xxx
287
+
288
+ // Follow-up - same sessionId = same trace, new span
289
+ const response2 = await client.prompts.execute('prm_abc123', {
290
+ provider: 'openai',
291
+ model: 'gpt-4o',
292
+ sessionId,
293
+ messages: [
294
+ { role: 'assistant', content: response1.content },
295
+ { role: 'user', content: 'Tell me more' },
296
+ ],
297
+ });
298
+
299
+ // response2.traceId === response1.traceId (same trace)
300
+ // response2.spanId !== response1.spanId (new span)
301
+ ```
302
+
303
+ #### Response Properties
304
+
305
+ Every execution returns observability IDs:
306
+
307
+ | Property | Type | Description |
308
+ |----------|------|-------------|
309
+ | `spanDataId` | `string` | Execution data ID (messages, response, usage) |
310
+ | `traceId` | `string` | Trace ID (groups related calls) |
311
+ | `spanId` | `string` | Span ID (this specific call) |
312
+
313
+ #### Custom Span Tracking
314
+
315
+ Track tool calls, retrieval operations, and custom logic as spans within a trace.
316
+
317
+ **Manual approach:**
318
+
319
+ ```typescript
320
+ // Create span
321
+ const span = await client.spans.create(traceId, {
322
+ type: 'tool',
323
+ toolName: 'fetch_weather',
324
+ toolArguments: { city: 'NYC' },
325
+ parentSpanId: generationSpanId,
326
+ });
327
+
328
+ // Execute
329
+ const weather = await fetchWeather('NYC');
330
+
331
+ // End span
332
+ await client.spans.end(span.id, {
333
+ status: 'completed',
334
+ toolResult: weather,
335
+ });
336
+ ```
337
+
338
+ **Wrapper approach:**
339
+
340
+ ```typescript
341
+ // wrapTool() - for tools
342
+ const weather = await client.spans.wrapTool(
343
+ { traceId, toolName: 'fetch_weather', parentSpanId },
344
+ { city: 'NYC' },
345
+ async (args) => fetchWeather(args.city),
346
+ );
347
+
348
+ // wrap() - for custom/retriever/embedding
349
+ const docs = await client.spans.wrap(
350
+ { traceId, type: 'retriever', name: 'vector_search' },
351
+ { query: 'how to...', topK: 5 },
352
+ async () => vectorDb.search(query),
353
+ );
354
+ ```
355
+
356
+ Wrappers automatically handle errors and set `status: 'error'` with message.
357
+
358
+ #### Span Types
359
+
360
+ | Type | Use Case |
361
+ |------|----------|
362
+ | `generation` | LLM calls (auto-created by `execute()`) |
363
+ | `tool` | Tool/function calls |
364
+ | `retriever` | RAG document retrieval |
365
+ | `embedding` | Embedding generation |
366
+ | `custom` | Any custom operation |
367
+
368
+ #### Viewing Traces
369
+
370
+ View your traces in the [Synova Cloud Dashboard](https://app.synova.cloud) under the Observability section. Each trace shows:
371
+ - All spans (LLM calls) in the session
372
+ - Input/output for each span
373
+ - Token usage and latency
374
+ - Error details if any
375
+
266
376
  ### Models
267
377
 
268
378
  #### List All Models
@@ -519,15 +629,25 @@ import type {
519
629
  ISynovaPromptVariable,
520
630
  ISynovaGetPromptOptions,
521
631
  // Execution
522
- ISynovaExecuteOptions,
632
+ ISynovaExecuteOptions, // includes sessionId
523
633
  ISynovaExecuteTypedOptions,
524
- ISynovaExecuteResponse,
634
+ ISynovaExecuteResponse, // includes spanDataId, traceId, spanId
525
635
  ISynovaExecutionUsage,
526
636
  ISynovaExecutionError,
527
637
  // Messages
528
638
  ISynovaMessage,
529
639
  TSynovaMessageRole,
530
640
  TSynovaResponseType,
641
+ // Spans
642
+ ISynovaSpan,
643
+ ISynovaSpanData,
644
+ ISynovaCreateSpanOptions,
645
+ ISynovaEndSpanOptions,
646
+ ISynovaWrapOptions,
647
+ ISynovaWrapToolOptions,
648
+ TSynovaSpanType,
649
+ TSynovaSpanStatus,
650
+ TSynovaSpanLevel,
531
651
  // Files
532
652
  ISynovaFileAttachment,
533
653
  ISynovaFileThumbnails,
package/dist/index.cjs CHANGED
@@ -107,7 +107,7 @@ var ValidationSynovaError = class extends SynovaError {
107
107
  };
108
108
 
109
109
  // src/version.ts
110
- var SDK_VERSION = "1.7.0" ;
110
+ var SDK_VERSION = "1.9.0" ;
111
111
 
112
112
  // src/utils/http.ts
113
113
  var HttpClient = class {
@@ -342,7 +342,9 @@ var SCHEMA_METADATA_KEYS = {
342
342
  // Array constraints
343
343
  MIN_ITEMS: "synova:schema:minItems",
344
344
  MAX_ITEMS: "synova:schema:maxItems",
345
- UNIQUE_ITEMS: "synova:schema:uniqueItems"
345
+ UNIQUE_ITEMS: "synova:schema:uniqueItems",
346
+ // Object constraints
347
+ ADDITIONAL_PROPERTIES: "synova:schema:additionalProperties"
346
348
  };
347
349
  function createMetadataDecorator(key, value) {
348
350
  return function(target, propertyKey) {
@@ -733,6 +735,12 @@ var ClassSchema = class {
733
735
  propertyName
734
736
  );
735
737
  if (uniqueItems) schema.uniqueItems = uniqueItems;
738
+ const additionalProperties = getSchemaMetadata(
739
+ SCHEMA_METADATA_KEYS.ADDITIONAL_PROPERTIES,
740
+ prototype,
741
+ propertyName
742
+ );
743
+ if (additionalProperties !== void 0) schema.additionalProperties = additionalProperties;
736
744
  }
737
745
  /**
738
746
  * Get required properties (properties without @IsOptional)
@@ -820,6 +828,7 @@ var PromptsResource = class {
820
828
  if (options.metadata !== void 0) body.metadata = options.metadata;
821
829
  if (options.parameters !== void 0) body.parameters = options.parameters;
822
830
  if (options.responseSchema !== void 0) body.responseSchema = options.responseSchema;
831
+ if (options.sessionId !== void 0) body.sessionId = options.sessionId;
823
832
  const response = await this.http.request({
824
833
  method: "POST",
825
834
  path: `/api/v1/prompts/${promptId}/run`,
@@ -1004,6 +1013,137 @@ var FilesResource = class {
1004
1013
  }
1005
1014
  };
1006
1015
 
1016
+ // src/resources/spans.ts
1017
+ var SpansResource = class {
1018
+ constructor(http) {
1019
+ this.http = http;
1020
+ }
1021
+ /**
1022
+ * Create a new span within a trace
1023
+ *
1024
+ * @param traceId - The trace ID to create span in
1025
+ * @param options - Span creation options
1026
+ * @returns Created span
1027
+ */
1028
+ async create(traceId, options) {
1029
+ const body = {
1030
+ type: options.type
1031
+ };
1032
+ if (options.parentSpanId !== void 0) body.parentSpanId = options.parentSpanId;
1033
+ if (options.name !== void 0) body.name = options.name;
1034
+ if (options.input !== void 0) body.input = options.input;
1035
+ if (options.toolName !== void 0) body.toolName = options.toolName;
1036
+ if (options.toolArguments !== void 0) body.toolArguments = options.toolArguments;
1037
+ if (options.metadata !== void 0) body.metadata = options.metadata;
1038
+ return this.http.request({
1039
+ method: "POST",
1040
+ path: `/api/v1/traces/${traceId}/spans`,
1041
+ body
1042
+ });
1043
+ }
1044
+ /**
1045
+ * End/complete a span
1046
+ *
1047
+ * @param spanId - The span ID to end
1048
+ * @param options - Span end options
1049
+ * @returns Updated span
1050
+ */
1051
+ async end(spanId, options) {
1052
+ const body = {};
1053
+ if (options?.status !== void 0) body.status = options.status;
1054
+ if (options?.level !== void 0) body.level = options.level;
1055
+ if (options?.statusMessage !== void 0) body.statusMessage = options.statusMessage;
1056
+ if (options?.output !== void 0) body.output = options.output;
1057
+ if (options?.toolResult !== void 0) body.toolResult = options.toolResult;
1058
+ if (options?.durationMs !== void 0) body.durationMs = options.durationMs;
1059
+ return this.http.request({
1060
+ method: "PATCH",
1061
+ path: `/api/v1/spans/${spanId}`,
1062
+ body
1063
+ });
1064
+ }
1065
+ /**
1066
+ * Wrap an async function with automatic span tracking
1067
+ *
1068
+ * @param options - Span options (traceId, type, name, etc.)
1069
+ * @param input - Input data to record in span
1070
+ * @param fn - Async function to execute
1071
+ * @returns Result of the function
1072
+ *
1073
+ * @example
1074
+ * ```ts
1075
+ * const result = await client.spans.wrap(
1076
+ * { traceId: 'trc_123', type: 'retriever', name: 'vector_search' },
1077
+ * { query: 'how to...', topK: 5 },
1078
+ * async () => vectorDb.search(query),
1079
+ * );
1080
+ * ```
1081
+ */
1082
+ async wrap(options, input, fn) {
1083
+ const span = await this.create(options.traceId, {
1084
+ type: options.type,
1085
+ name: options.name,
1086
+ parentSpanId: options.parentSpanId,
1087
+ input,
1088
+ metadata: options.metadata
1089
+ });
1090
+ try {
1091
+ const result = await fn();
1092
+ await this.end(span.id, {
1093
+ status: "completed",
1094
+ output: result
1095
+ });
1096
+ return result;
1097
+ } catch (error) {
1098
+ await this.end(span.id, {
1099
+ status: "error",
1100
+ statusMessage: error instanceof Error ? error.message : String(error)
1101
+ });
1102
+ throw error;
1103
+ }
1104
+ }
1105
+ /**
1106
+ * Wrap a tool function with automatic span tracking
1107
+ *
1108
+ * @param options - Tool span options (traceId, toolName, etc.)
1109
+ * @param args - Tool arguments to record
1110
+ * @param fn - Tool function to execute
1111
+ * @returns Result of the tool
1112
+ *
1113
+ * @example
1114
+ * ```ts
1115
+ * const weather = await client.spans.wrapTool(
1116
+ * { traceId: 'trc_123', toolName: 'fetch_weather', parentSpanId: 'spn_abc' },
1117
+ * { city: 'NYC' },
1118
+ * async (args) => fetchWeather(args.city),
1119
+ * );
1120
+ * ```
1121
+ */
1122
+ async wrapTool(options, args, fn) {
1123
+ const span = await this.create(options.traceId, {
1124
+ type: "tool",
1125
+ toolName: options.toolName,
1126
+ toolArguments: args,
1127
+ parentSpanId: options.parentSpanId,
1128
+ metadata: options.metadata
1129
+ });
1130
+ try {
1131
+ const result = await fn(args);
1132
+ await this.end(span.id, {
1133
+ status: "completed",
1134
+ toolResult: result
1135
+ });
1136
+ return result;
1137
+ } catch (error) {
1138
+ await this.end(span.id, {
1139
+ status: "error",
1140
+ statusMessage: error instanceof Error ? error.message : String(error)
1141
+ });
1142
+ throw error;
1143
+ }
1144
+ }
1145
+ };
1146
+
1007
1147
  // src/client.ts
1008
1148
  var DEFAULT_BASE_URL = "https://api.synova.cloud";
1009
1149
  var DEFAULT_TIMEOUT = 3e4;
@@ -1024,6 +1164,7 @@ var SynovaCloudSdk = class {
1024
1164
  prompts;
1025
1165
  models;
1026
1166
  files;
1167
+ spans;
1027
1168
  http;
1028
1169
  /**
1029
1170
  * Create a new Synova Cloud SDK client
@@ -1052,6 +1193,7 @@ var SynovaCloudSdk = class {
1052
1193
  this.prompts = new PromptsResource(this.http);
1053
1194
  this.models = new ModelsResource(this.http);
1054
1195
  this.files = new FilesResource(this.http);
1196
+ this.spans = new SpansResource(this.http);
1055
1197
  }
1056
1198
  };
1057
1199