@grafana/sigil-sdk-js 0.0.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.
- package/LICENSE +3 -0
- package/README.md +367 -0
- package/dist/client.d.ts +102 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1502 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +190 -0
- package/dist/config.js.map +1 -0
- package/dist/content_capture.d.ts +35 -0
- package/dist/content_capture.d.ts.map +1 -0
- package/dist/content_capture.js +123 -0
- package/dist/content_capture.js.map +1 -0
- package/dist/context.d.ts +11 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +53 -0
- package/dist/context.js.map +1 -0
- package/dist/exporters/default.d.ts +3 -0
- package/dist/exporters/default.d.ts.map +1 -0
- package/dist/exporters/default.js +34 -0
- package/dist/exporters/default.js.map +1 -0
- package/dist/exporters/grpc.d.ts +14 -0
- package/dist/exporters/grpc.d.ts.map +1 -0
- package/dist/exporters/grpc.js +369 -0
- package/dist/exporters/grpc.js.map +1 -0
- package/dist/exporters/http.d.ts +8 -0
- package/dist/exporters/http.d.ts.map +1 -0
- package/dist/exporters/http.js +258 -0
- package/dist/exporters/http.js.map +1 -0
- package/dist/frameworks/google-adk/index.d.ts +136 -0
- package/dist/frameworks/google-adk/index.d.ts.map +1 -0
- package/dist/frameworks/google-adk/index.js +509 -0
- package/dist/frameworks/google-adk/index.js.map +1 -0
- package/dist/frameworks/langchain/index.d.ts +29 -0
- package/dist/frameworks/langchain/index.d.ts.map +1 -0
- package/dist/frameworks/langchain/index.js +70 -0
- package/dist/frameworks/langchain/index.js.map +1 -0
- package/dist/frameworks/langgraph/index.d.ts +29 -0
- package/dist/frameworks/langgraph/index.d.ts.map +1 -0
- package/dist/frameworks/langgraph/index.js +70 -0
- package/dist/frameworks/langgraph/index.js.map +1 -0
- package/dist/frameworks/llamaindex/index.d.ts +43 -0
- package/dist/frameworks/llamaindex/index.d.ts.map +1 -0
- package/dist/frameworks/llamaindex/index.js +493 -0
- package/dist/frameworks/llamaindex/index.js.map +1 -0
- package/dist/frameworks/openai-agents/index.d.ts +33 -0
- package/dist/frameworks/openai-agents/index.d.ts.map +1 -0
- package/dist/frameworks/openai-agents/index.js +531 -0
- package/dist/frameworks/openai-agents/index.js.map +1 -0
- package/dist/frameworks/shared.d.ts +57 -0
- package/dist/frameworks/shared.d.ts.map +1 -0
- package/dist/frameworks/shared.js +1032 -0
- package/dist/frameworks/shared.js.map +1 -0
- package/dist/frameworks/vercel-ai-sdk/hooks.d.ts +18 -0
- package/dist/frameworks/vercel-ai-sdk/hooks.d.ts.map +1 -0
- package/dist/frameworks/vercel-ai-sdk/hooks.js +672 -0
- package/dist/frameworks/vercel-ai-sdk/hooks.js.map +1 -0
- package/dist/frameworks/vercel-ai-sdk/index.d.ts +8 -0
- package/dist/frameworks/vercel-ai-sdk/index.d.ts.map +1 -0
- package/dist/frameworks/vercel-ai-sdk/index.js +7 -0
- package/dist/frameworks/vercel-ai-sdk/index.js.map +1 -0
- package/dist/frameworks/vercel-ai-sdk/mapping.d.ts +49 -0
- package/dist/frameworks/vercel-ai-sdk/mapping.d.ts.map +1 -0
- package/dist/frameworks/vercel-ai-sdk/mapping.js +660 -0
- package/dist/frameworks/vercel-ai-sdk/mapping.js.map +1 -0
- package/dist/frameworks/vercel-ai-sdk/types.d.ts +126 -0
- package/dist/frameworks/vercel-ai-sdk/types.d.ts.map +1 -0
- package/dist/frameworks/vercel-ai-sdk/types.js +2 -0
- package/dist/frameworks/vercel-ai-sdk/types.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/anthropic.d.ts +35 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +581 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/gemini.d.ts +42 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +650 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/openai.d.ts +67 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +1007 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/types.d.ts +461 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +25 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +331 -0
- package/dist/utils.js.map +1 -0
- package/docs/frameworks/google-adk.md +84 -0
- package/docs/frameworks/langchain.md +102 -0
- package/docs/frameworks/langgraph.md +137 -0
- package/docs/frameworks/llamaindex.md +82 -0
- package/docs/frameworks/openai-agents.md +88 -0
- package/docs/frameworks/vercel-ai-sdk.md +124 -0
- package/docs/index.md +22 -0
- package/docs/providers/anthropic.md +69 -0
- package/docs/providers/gemini.md +86 -0
- package/docs/providers/openai.md +151 -0
- package/package.json +80 -0
- package/proto/opentelemetry/proto/collector/trace/v1/trace_service.proto +77 -0
- package/proto/opentelemetry/proto/common/v1/common.proto +115 -0
- package/proto/opentelemetry/proto/resource/v1/resource.proto +44 -0
- package/proto/opentelemetry/proto/trace/v1/trace.proto +357 -0
- package/proto/sigil/v1/generation_ingest.proto +145 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,1502 @@
|
|
|
1
|
+
import { metrics, SpanKind, SpanStatusCode, trace, } from '@opentelemetry/api';
|
|
2
|
+
import { defaultLogger, mergeConfig } from './config.js';
|
|
3
|
+
import { callContentCaptureResolver, resolveClientContentCaptureMode, resolveContentCaptureMode, shouldIncludeToolContent, stampContentCaptureMetadata, stripContent, } from './content_capture.js';
|
|
4
|
+
import { agentNameFromContext, agentVersionFromContext, conversationIdFromContext, conversationTitleFromContext, userIdFromContext, } from './context.js';
|
|
5
|
+
import { createDefaultGenerationExporter } from './exporters/default.js';
|
|
6
|
+
import { asError, cloneArtifact, cloneEmbeddingResult, cloneEmbeddingStart, cloneGeneration, cloneGenerationResult, cloneGenerationStart, cloneMessage, cloneModelRef, cloneToolDefinition, cloneToolExecution, cloneToolExecutionResult, cloneToolExecutionStart, defaultOperationNameForMode, defaultSleep, encodedSizeBytes, maybeUnref, newLocalID, validateEmbeddingResult, validateEmbeddingStart, validateGeneration, validateToolExecution, } from './utils.js';
|
|
7
|
+
const spanAttrGenerationID = 'sigil.generation.id';
|
|
8
|
+
const spanAttrSDKName = 'sigil.sdk.name';
|
|
9
|
+
const spanAttrFrameworkRunID = 'sigil.framework.run_id';
|
|
10
|
+
const spanAttrFrameworkThreadID = 'sigil.framework.thread_id';
|
|
11
|
+
const spanAttrFrameworkParentRunID = 'sigil.framework.parent_run_id';
|
|
12
|
+
const spanAttrFrameworkComponentName = 'sigil.framework.component_name';
|
|
13
|
+
const spanAttrFrameworkRunType = 'sigil.framework.run_type';
|
|
14
|
+
const spanAttrFrameworkRetryAttempt = 'sigil.framework.retry_attempt';
|
|
15
|
+
const spanAttrFrameworkLangGraphNode = 'sigil.framework.langgraph.node';
|
|
16
|
+
const spanAttrFrameworkEventID = 'sigil.framework.event_id';
|
|
17
|
+
const spanAttrConversationID = 'gen_ai.conversation.id';
|
|
18
|
+
const spanAttrConversationTitle = 'sigil.conversation.title';
|
|
19
|
+
const spanAttrUserID = 'user.id';
|
|
20
|
+
const spanAttrAgentName = 'gen_ai.agent.name';
|
|
21
|
+
const spanAttrAgentVersion = 'gen_ai.agent.version';
|
|
22
|
+
const spanAttrErrorType = 'error.type';
|
|
23
|
+
const spanAttrErrorCategory = 'error.category';
|
|
24
|
+
const spanAttrOperationName = 'gen_ai.operation.name';
|
|
25
|
+
const spanAttrProviderName = 'gen_ai.provider.name';
|
|
26
|
+
const spanAttrRequestModel = 'gen_ai.request.model';
|
|
27
|
+
const spanAttrRequestMaxTokens = 'gen_ai.request.max_tokens';
|
|
28
|
+
const spanAttrRequestTemperature = 'gen_ai.request.temperature';
|
|
29
|
+
const spanAttrRequestTopP = 'gen_ai.request.top_p';
|
|
30
|
+
const spanAttrRequestToolChoice = 'sigil.gen_ai.request.tool_choice';
|
|
31
|
+
const spanAttrRequestThinkingEnabled = 'sigil.gen_ai.request.thinking.enabled';
|
|
32
|
+
const spanAttrRequestThinkingBudget = 'sigil.gen_ai.request.thinking.budget_tokens';
|
|
33
|
+
const spanAttrResponseID = 'gen_ai.response.id';
|
|
34
|
+
const spanAttrResponseModel = 'gen_ai.response.model';
|
|
35
|
+
const spanAttrFinishReasons = 'gen_ai.response.finish_reasons';
|
|
36
|
+
const spanAttrInputTokens = 'gen_ai.usage.input_tokens';
|
|
37
|
+
const spanAttrOutputTokens = 'gen_ai.usage.output_tokens';
|
|
38
|
+
const spanAttrEmbeddingInputCount = 'gen_ai.embeddings.input_count';
|
|
39
|
+
const spanAttrEmbeddingInputTexts = 'gen_ai.embeddings.input_texts';
|
|
40
|
+
const spanAttrEmbeddingDimCount = 'gen_ai.embeddings.dimension.count';
|
|
41
|
+
const spanAttrRequestEncodingFormats = 'gen_ai.request.encoding_formats';
|
|
42
|
+
const spanAttrCacheReadTokens = 'gen_ai.usage.cache_read_input_tokens';
|
|
43
|
+
const spanAttrCacheWriteTokens = 'gen_ai.usage.cache_write_input_tokens';
|
|
44
|
+
const spanAttrCacheCreationTokens = 'gen_ai.usage.cache_creation_input_tokens';
|
|
45
|
+
const spanAttrReasoningTokens = 'gen_ai.usage.reasoning_tokens';
|
|
46
|
+
const spanAttrToolName = 'gen_ai.tool.name';
|
|
47
|
+
const spanAttrToolCallID = 'gen_ai.tool.call.id';
|
|
48
|
+
const spanAttrToolType = 'gen_ai.tool.type';
|
|
49
|
+
const spanAttrToolDescription = 'gen_ai.tool.description';
|
|
50
|
+
const spanAttrToolCallArguments = 'gen_ai.tool.call.arguments';
|
|
51
|
+
const spanAttrToolCallResult = 'gen_ai.tool.call.result';
|
|
52
|
+
const maxRatingConversationIdLen = 255;
|
|
53
|
+
const maxRatingIdLen = 128;
|
|
54
|
+
const maxRatingGenerationIdLen = 255;
|
|
55
|
+
const maxRatingActorIdLen = 255;
|
|
56
|
+
const maxRatingSourceLen = 64;
|
|
57
|
+
const maxRatingCommentBytes = 4096;
|
|
58
|
+
const maxRatingMetadataBytes = 16 * 1024;
|
|
59
|
+
const metricOperationDuration = 'gen_ai.client.operation.duration';
|
|
60
|
+
const metricTokenUsage = 'gen_ai.client.token.usage';
|
|
61
|
+
const metricTimeToFirstToken = 'gen_ai.client.time_to_first_token';
|
|
62
|
+
const metricToolCallsPerOperation = 'gen_ai.client.tool_calls_per_operation';
|
|
63
|
+
const metricAttrTokenType = 'gen_ai.token.type';
|
|
64
|
+
const metricTokenTypeInput = 'input';
|
|
65
|
+
const metricTokenTypeOutput = 'output';
|
|
66
|
+
const metricTokenTypeCacheRead = 'cache_read';
|
|
67
|
+
const metricTokenTypeCacheWrite = 'cache_write';
|
|
68
|
+
const metricTokenTypeCacheCreation = 'cache_creation';
|
|
69
|
+
const metricTokenTypeReasoning = 'reasoning';
|
|
70
|
+
const instrumentationName = 'github.com/grafana/sigil/sdks/js';
|
|
71
|
+
const sdkName = 'sdk-js';
|
|
72
|
+
const defaultEmbeddingOperationName = 'embeddings';
|
|
73
|
+
const metadataUserIDKey = 'sigil.user.id';
|
|
74
|
+
const metadataLegacyUserIDKey = 'user.id';
|
|
75
|
+
export class SigilClient {
|
|
76
|
+
config;
|
|
77
|
+
nowFn;
|
|
78
|
+
sleepFn;
|
|
79
|
+
logger;
|
|
80
|
+
generationExporter;
|
|
81
|
+
tracer;
|
|
82
|
+
meter;
|
|
83
|
+
operationDurationHistogram;
|
|
84
|
+
tokenUsageHistogram;
|
|
85
|
+
ttftHistogram;
|
|
86
|
+
toolCallsHistogram;
|
|
87
|
+
generations = [];
|
|
88
|
+
toolExecutions = [];
|
|
89
|
+
pendingGenerations = [];
|
|
90
|
+
flushPromise;
|
|
91
|
+
flushRequested = false;
|
|
92
|
+
flushTimer;
|
|
93
|
+
shutdownPromise;
|
|
94
|
+
shuttingDown = false;
|
|
95
|
+
closed = false;
|
|
96
|
+
/**
|
|
97
|
+
* Creates a Sigil SDK client.
|
|
98
|
+
*
|
|
99
|
+
* `inputConfig` is merged with defaults.
|
|
100
|
+
*/
|
|
101
|
+
constructor(inputConfig = {}) {
|
|
102
|
+
this.config = mergeConfig(inputConfig);
|
|
103
|
+
this.nowFn = this.config.now ?? (() => new Date());
|
|
104
|
+
this.sleepFn = this.config.sleep ?? defaultSleep;
|
|
105
|
+
this.logger = this.config.logger ?? defaultLogger;
|
|
106
|
+
this.generationExporter =
|
|
107
|
+
this.config.generationExporter ?? createDefaultGenerationExporter(this.config.generationExport);
|
|
108
|
+
this.tracer = this.config.tracer ?? trace.getTracer(instrumentationName);
|
|
109
|
+
this.meter = this.config.meter ?? metrics.getMeter(instrumentationName);
|
|
110
|
+
this.operationDurationHistogram = this.meter.createHistogram(metricOperationDuration, { unit: 's' });
|
|
111
|
+
this.tokenUsageHistogram = this.meter.createHistogram(metricTokenUsage, { unit: 'token' });
|
|
112
|
+
this.ttftHistogram = this.meter.createHistogram(metricTimeToFirstToken, { unit: 's' });
|
|
113
|
+
this.toolCallsHistogram = this.meter.createHistogram(metricToolCallsPerOperation, { unit: 'count' });
|
|
114
|
+
if (this.config.generationExport.flushIntervalMs > 0) {
|
|
115
|
+
this.flushTimer = setInterval(() => {
|
|
116
|
+
this.triggerAsyncFlush();
|
|
117
|
+
}, this.config.generationExport.flushIntervalMs);
|
|
118
|
+
maybeUnref(this.flushTimer);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
startGeneration(start, callback) {
|
|
122
|
+
return this.startGenerationWithMode(start, 'SYNC', callback);
|
|
123
|
+
}
|
|
124
|
+
startStreamingGeneration(start, callback) {
|
|
125
|
+
return this.startGenerationWithMode(start, 'STREAM', callback);
|
|
126
|
+
}
|
|
127
|
+
startEmbedding(start, callback) {
|
|
128
|
+
this.assertOpen();
|
|
129
|
+
const seed = cloneEmbeddingStart(start);
|
|
130
|
+
if (!notEmpty(seed.agentName)) {
|
|
131
|
+
seed.agentName = agentNameFromContext();
|
|
132
|
+
}
|
|
133
|
+
if (!notEmpty(seed.agentVersion)) {
|
|
134
|
+
seed.agentVersion = agentVersionFromContext();
|
|
135
|
+
}
|
|
136
|
+
const recorder = new EmbeddingRecorderImpl(this, seed);
|
|
137
|
+
if (callback === undefined) {
|
|
138
|
+
return recorder;
|
|
139
|
+
}
|
|
140
|
+
return runWithRecorder(recorder, callback);
|
|
141
|
+
}
|
|
142
|
+
startToolExecution(start, callback) {
|
|
143
|
+
this.assertOpen();
|
|
144
|
+
const recorder = start.toolName.trim().length === 0 ? new NoopToolExecutionRecorder() : new ToolExecutionRecorderImpl(this, start);
|
|
145
|
+
if (callback === undefined) {
|
|
146
|
+
return recorder;
|
|
147
|
+
}
|
|
148
|
+
return runWithRecorder(recorder, callback);
|
|
149
|
+
}
|
|
150
|
+
/** Submits a user-facing conversation rating through Sigil HTTP API. */
|
|
151
|
+
async submitConversationRating(conversationId, input) {
|
|
152
|
+
this.assertOpen();
|
|
153
|
+
const normalizedConversationId = conversationId.trim();
|
|
154
|
+
if (normalizedConversationId.length === 0) {
|
|
155
|
+
throw new Error('sigil conversation rating validation failed: conversationId is required');
|
|
156
|
+
}
|
|
157
|
+
if (normalizedConversationId.length > maxRatingConversationIdLen) {
|
|
158
|
+
throw new Error('sigil conversation rating validation failed: conversationId is too long');
|
|
159
|
+
}
|
|
160
|
+
const normalizedInput = normalizeConversationRatingInput(input);
|
|
161
|
+
const endpoint = buildConversationRatingEndpoint(this.config.api.endpoint, this.config.generationExport.insecure, normalizedConversationId);
|
|
162
|
+
const requestBody = {
|
|
163
|
+
rating_id: normalizedInput.ratingId,
|
|
164
|
+
rating: normalizedInput.rating,
|
|
165
|
+
comment: normalizedInput.comment,
|
|
166
|
+
metadata: normalizedInput.metadata,
|
|
167
|
+
generation_id: normalizedInput.generationId,
|
|
168
|
+
rater_id: normalizedInput.raterId,
|
|
169
|
+
source: normalizedInput.source,
|
|
170
|
+
};
|
|
171
|
+
const response = await fetch(endpoint, {
|
|
172
|
+
method: 'POST',
|
|
173
|
+
headers: {
|
|
174
|
+
'content-type': 'application/json',
|
|
175
|
+
...this.config.generationExport.headers,
|
|
176
|
+
},
|
|
177
|
+
body: JSON.stringify(requestBody),
|
|
178
|
+
});
|
|
179
|
+
const responseText = (await response.text()).trim();
|
|
180
|
+
if (response.status === 400) {
|
|
181
|
+
throw new Error(`sigil conversation rating validation failed: ${ratingErrorText(responseText, response.status)}`);
|
|
182
|
+
}
|
|
183
|
+
if (response.status === 409) {
|
|
184
|
+
throw new Error(`sigil conversation rating conflict: ${ratingErrorText(responseText, response.status)}`);
|
|
185
|
+
}
|
|
186
|
+
if (!response.ok) {
|
|
187
|
+
throw new Error(`sigil conversation rating transport failed: status ${response.status}: ${ratingErrorText(responseText, response.status)}`);
|
|
188
|
+
}
|
|
189
|
+
if (responseText.length === 0) {
|
|
190
|
+
throw new Error('sigil conversation rating transport failed: empty response payload');
|
|
191
|
+
}
|
|
192
|
+
let payload;
|
|
193
|
+
try {
|
|
194
|
+
payload = JSON.parse(responseText);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
throw new Error(`sigil conversation rating transport failed: invalid JSON response: ${asError(error).message}`);
|
|
198
|
+
}
|
|
199
|
+
return parseSubmitConversationRatingResponse(payload);
|
|
200
|
+
}
|
|
201
|
+
/** Forces immediate drain of queued generation exports. */
|
|
202
|
+
async flush() {
|
|
203
|
+
this.assertOpen();
|
|
204
|
+
await this.flushInternal();
|
|
205
|
+
}
|
|
206
|
+
/** Flushes pending generations and shuts down the generation exporter. */
|
|
207
|
+
async shutdown() {
|
|
208
|
+
if (this.shutdownPromise !== undefined) {
|
|
209
|
+
await this.shutdownPromise;
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
this.shuttingDown = true;
|
|
213
|
+
this.shutdownPromise = (async () => {
|
|
214
|
+
this.stopFlushTimer();
|
|
215
|
+
try {
|
|
216
|
+
await this.flushInternal();
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
this.logWarn('sigil generation export flush on shutdown failed', error);
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
await this.generationExporter.shutdown?.();
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
this.logWarn('sigil generation exporter shutdown failed', error);
|
|
226
|
+
}
|
|
227
|
+
this.closed = true;
|
|
228
|
+
})();
|
|
229
|
+
await this.shutdownPromise;
|
|
230
|
+
}
|
|
231
|
+
/** Returns a cloned in-memory snapshot for debugging and tests. */
|
|
232
|
+
debugSnapshot() {
|
|
233
|
+
return {
|
|
234
|
+
generations: this.generations.map(cloneGeneration),
|
|
235
|
+
toolExecutions: this.toolExecutions.map(cloneToolExecution),
|
|
236
|
+
queueSize: this.pendingGenerations.length,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
internalNow() {
|
|
240
|
+
return this.nowFn();
|
|
241
|
+
}
|
|
242
|
+
internalRecordGeneration(generation) {
|
|
243
|
+
this.generations.push(cloneGeneration(generation));
|
|
244
|
+
}
|
|
245
|
+
internalRecordToolExecution(toolExecution) {
|
|
246
|
+
this.toolExecutions.push(cloneToolExecution(toolExecution));
|
|
247
|
+
}
|
|
248
|
+
internalEnqueueGeneration(generation) {
|
|
249
|
+
if (this.shuttingDown || this.closed) {
|
|
250
|
+
throw new Error('sigil client is shutdown');
|
|
251
|
+
}
|
|
252
|
+
const payloadMaxBytes = this.config.generationExport.payloadMaxBytes;
|
|
253
|
+
if (payloadMaxBytes > 0) {
|
|
254
|
+
const payloadBytes = encodedSizeBytes(generation);
|
|
255
|
+
if (payloadBytes > payloadMaxBytes) {
|
|
256
|
+
throw new Error(`generation payload exceeds max bytes (${payloadBytes} > ${payloadMaxBytes})`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const queueSize = Math.max(1, this.config.generationExport.queueSize);
|
|
260
|
+
if (this.pendingGenerations.length >= queueSize) {
|
|
261
|
+
throw new Error('generation queue is full');
|
|
262
|
+
}
|
|
263
|
+
this.pendingGenerations.push(cloneGeneration(generation));
|
|
264
|
+
const batchSize = Math.max(1, this.config.generationExport.batchSize);
|
|
265
|
+
if (this.pendingGenerations.length >= batchSize) {
|
|
266
|
+
this.triggerAsyncFlush();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
internalLogWarn(message, error) {
|
|
270
|
+
this.logWarn(message, error);
|
|
271
|
+
}
|
|
272
|
+
internalResolveGenerationContentCaptureMode(seed) {
|
|
273
|
+
const resolverMode = callContentCaptureResolver(this.config.contentCaptureResolver, seed.metadata);
|
|
274
|
+
const clientMode = resolveClientContentCaptureMode(resolveContentCaptureMode(resolverMode, this.config.contentCapture));
|
|
275
|
+
return resolveContentCaptureMode(seed.contentCapture ?? 'default', clientMode);
|
|
276
|
+
}
|
|
277
|
+
internalResolveToolIncludeContent(seed) {
|
|
278
|
+
const resolverMode = callContentCaptureResolver(this.config.contentCaptureResolver, undefined);
|
|
279
|
+
return shouldIncludeToolContent(seed.contentCapture ?? 'default', this.config.contentCapture, resolverMode, seed.includeContent ?? false);
|
|
280
|
+
}
|
|
281
|
+
internalStartGenerationSpan(seed, mode, startedAt) {
|
|
282
|
+
const operationName = seed.operationName ?? defaultOperationNameForMode(mode);
|
|
283
|
+
const span = this.tracer.startSpan(generationSpanName(operationName, seed.model.name), {
|
|
284
|
+
kind: SpanKind.CLIENT,
|
|
285
|
+
startTime: startedAt,
|
|
286
|
+
});
|
|
287
|
+
setGenerationSpanAttributes(span, {
|
|
288
|
+
id: seed.id,
|
|
289
|
+
conversationId: seed.conversationId,
|
|
290
|
+
conversationTitle: seed.conversationTitle,
|
|
291
|
+
userId: seed.userId,
|
|
292
|
+
agentName: seed.agentName,
|
|
293
|
+
agentVersion: seed.agentVersion,
|
|
294
|
+
operationName,
|
|
295
|
+
model: seed.model,
|
|
296
|
+
maxTokens: seed.maxTokens,
|
|
297
|
+
temperature: seed.temperature,
|
|
298
|
+
topP: seed.topP,
|
|
299
|
+
toolChoice: seed.toolChoice,
|
|
300
|
+
thinkingEnabled: seed.thinkingEnabled,
|
|
301
|
+
metadata: seed.metadata,
|
|
302
|
+
});
|
|
303
|
+
return span;
|
|
304
|
+
}
|
|
305
|
+
internalStartEmbeddingSpan(seed, startedAt) {
|
|
306
|
+
const span = this.tracer.startSpan(embeddingSpanName(seed.model.name), {
|
|
307
|
+
kind: SpanKind.CLIENT,
|
|
308
|
+
startTime: startedAt,
|
|
309
|
+
});
|
|
310
|
+
setEmbeddingStartSpanAttributes(span, seed);
|
|
311
|
+
return span;
|
|
312
|
+
}
|
|
313
|
+
internalStartToolExecutionSpan(seed, startedAt) {
|
|
314
|
+
const span = this.tracer.startSpan(toolSpanName(seed.toolName), {
|
|
315
|
+
kind: SpanKind.INTERNAL,
|
|
316
|
+
startTime: startedAt,
|
|
317
|
+
});
|
|
318
|
+
setToolSpanAttributes(span, seed);
|
|
319
|
+
return span;
|
|
320
|
+
}
|
|
321
|
+
internalApplyTraceContextFromSpan(span, generation) {
|
|
322
|
+
const context = span.spanContext();
|
|
323
|
+
if (context.traceId.length > 0) {
|
|
324
|
+
generation.traceId = context.traceId;
|
|
325
|
+
}
|
|
326
|
+
if (context.spanId.length > 0) {
|
|
327
|
+
generation.spanId = context.spanId;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
internalSyncGenerationSpan(span, generation) {
|
|
331
|
+
setGenerationSpanAttributes(span, generation);
|
|
332
|
+
}
|
|
333
|
+
internalClearSpanConversationTitle(span) {
|
|
334
|
+
span.setAttribute(spanAttrConversationTitle, '');
|
|
335
|
+
}
|
|
336
|
+
internalFinalizeGenerationSpan(span, generation, callError, validationError, enqueueError, firstTokenAt, precomputedCallErrorCategory) {
|
|
337
|
+
span.updateName(generationSpanName(generation.operationName, generation.model.name));
|
|
338
|
+
if (callError !== undefined) {
|
|
339
|
+
span.recordException(new Error(callError));
|
|
340
|
+
}
|
|
341
|
+
if (validationError !== undefined) {
|
|
342
|
+
span.recordException(validationError);
|
|
343
|
+
}
|
|
344
|
+
if (enqueueError !== undefined) {
|
|
345
|
+
span.recordException(enqueueError);
|
|
346
|
+
}
|
|
347
|
+
let errorType = '';
|
|
348
|
+
let errorCategory = '';
|
|
349
|
+
if (callError !== undefined) {
|
|
350
|
+
errorType = 'provider_call_error';
|
|
351
|
+
errorCategory = precomputedCallErrorCategory ?? errorCategoryFromError(callError, true);
|
|
352
|
+
span.setAttribute(spanAttrErrorType, errorType);
|
|
353
|
+
span.setAttribute(spanAttrErrorCategory, errorCategory);
|
|
354
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: callError });
|
|
355
|
+
}
|
|
356
|
+
else if (validationError !== undefined) {
|
|
357
|
+
errorType = 'validation_error';
|
|
358
|
+
errorCategory = 'sdk_error';
|
|
359
|
+
span.setAttribute(spanAttrErrorType, errorType);
|
|
360
|
+
span.setAttribute(spanAttrErrorCategory, errorCategory);
|
|
361
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: validationError.message });
|
|
362
|
+
}
|
|
363
|
+
else if (enqueueError !== undefined) {
|
|
364
|
+
errorType = 'enqueue_error';
|
|
365
|
+
errorCategory = 'sdk_error';
|
|
366
|
+
span.setAttribute(spanAttrErrorType, errorType);
|
|
367
|
+
span.setAttribute(spanAttrErrorCategory, errorCategory);
|
|
368
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: enqueueError.message });
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
372
|
+
}
|
|
373
|
+
this.recordGenerationMetrics(generation, errorType, errorCategory, firstTokenAt);
|
|
374
|
+
span.end(generation.completedAt);
|
|
375
|
+
}
|
|
376
|
+
internalFinalizeEmbeddingSpan(span, seed, result, hasResult, callError, localError, startedAt, completedAt) {
|
|
377
|
+
span.updateName(embeddingSpanName(seed.model.name));
|
|
378
|
+
setEmbeddingEndSpanAttributes(span, result, hasResult, this.config.embeddingCapture);
|
|
379
|
+
if (callError !== undefined) {
|
|
380
|
+
span.recordException(callError);
|
|
381
|
+
}
|
|
382
|
+
if (localError !== undefined) {
|
|
383
|
+
span.recordException(localError);
|
|
384
|
+
}
|
|
385
|
+
let errorType = '';
|
|
386
|
+
let errorCategory = '';
|
|
387
|
+
if (callError !== undefined) {
|
|
388
|
+
errorType = 'provider_call_error';
|
|
389
|
+
errorCategory = errorCategoryFromError(callError, true);
|
|
390
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: callError.message });
|
|
391
|
+
}
|
|
392
|
+
else if (localError !== undefined) {
|
|
393
|
+
errorType = 'validation_error';
|
|
394
|
+
errorCategory = 'sdk_error';
|
|
395
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: localError.message });
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
399
|
+
}
|
|
400
|
+
if (errorType.length > 0) {
|
|
401
|
+
span.setAttribute(spanAttrErrorType, errorType);
|
|
402
|
+
span.setAttribute(spanAttrErrorCategory, errorCategory);
|
|
403
|
+
}
|
|
404
|
+
this.recordEmbeddingMetrics(seed, result, startedAt, completedAt, errorType, errorCategory);
|
|
405
|
+
span.end(completedAt);
|
|
406
|
+
}
|
|
407
|
+
internalFinalizeToolExecutionSpan(span, toolExecution, localError) {
|
|
408
|
+
setToolSpanAttributes(span, toolExecution);
|
|
409
|
+
if (toolExecution.includeContent) {
|
|
410
|
+
const argumentsResult = serializeToolContent(toolExecution.arguments);
|
|
411
|
+
if (argumentsResult.error !== undefined && localError === undefined) {
|
|
412
|
+
localError = argumentsResult.error;
|
|
413
|
+
}
|
|
414
|
+
else if (argumentsResult.value !== undefined) {
|
|
415
|
+
span.setAttribute(spanAttrToolCallArguments, argumentsResult.value);
|
|
416
|
+
}
|
|
417
|
+
const resultValue = serializeToolContent(toolExecution.result);
|
|
418
|
+
if (resultValue.error !== undefined && localError === undefined) {
|
|
419
|
+
localError = resultValue.error;
|
|
420
|
+
}
|
|
421
|
+
else if (resultValue.value !== undefined) {
|
|
422
|
+
span.setAttribute(spanAttrToolCallResult, resultValue.value);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (toolExecution.callError !== undefined) {
|
|
426
|
+
span.recordException(new Error(toolExecution.callError));
|
|
427
|
+
span.setAttribute(spanAttrErrorType, 'tool_execution_error');
|
|
428
|
+
span.setAttribute(spanAttrErrorCategory, errorCategoryFromError(toolExecution.callError, true));
|
|
429
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: toolExecution.callError });
|
|
430
|
+
}
|
|
431
|
+
else if (localError !== undefined) {
|
|
432
|
+
span.recordException(localError);
|
|
433
|
+
span.setAttribute(spanAttrErrorType, 'tool_execution_error');
|
|
434
|
+
span.setAttribute(spanAttrErrorCategory, errorCategoryFromError(localError, true));
|
|
435
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: localError.message });
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
439
|
+
}
|
|
440
|
+
this.recordToolExecutionMetrics(toolExecution, localError ?? (toolExecution.callError !== undefined ? new Error(toolExecution.callError) : undefined));
|
|
441
|
+
span.end(toolExecution.completedAt);
|
|
442
|
+
return localError;
|
|
443
|
+
}
|
|
444
|
+
recordGenerationMetrics(generation, errorType, errorCategory, firstTokenAt) {
|
|
445
|
+
const startedMs = generation.startedAt.getTime();
|
|
446
|
+
const completedMs = generation.completedAt.getTime();
|
|
447
|
+
const durationSeconds = Math.max(0, (completedMs - startedMs) / 1_000);
|
|
448
|
+
this.operationDurationHistogram.record(durationSeconds, {
|
|
449
|
+
[spanAttrOperationName]: generation.operationName,
|
|
450
|
+
[spanAttrProviderName]: generation.model.provider,
|
|
451
|
+
[spanAttrRequestModel]: generation.model.name,
|
|
452
|
+
[spanAttrAgentName]: generation.agentName ?? '',
|
|
453
|
+
[spanAttrErrorType]: errorType,
|
|
454
|
+
[spanAttrErrorCategory]: errorCategory,
|
|
455
|
+
});
|
|
456
|
+
const usage = generation.usage;
|
|
457
|
+
if (usage !== undefined) {
|
|
458
|
+
this.recordTokenUsage(generation, metricTokenTypeInput, usage.inputTokens);
|
|
459
|
+
this.recordTokenUsage(generation, metricTokenTypeOutput, usage.outputTokens);
|
|
460
|
+
this.recordTokenUsage(generation, metricTokenTypeCacheRead, usage.cacheReadInputTokens);
|
|
461
|
+
this.recordTokenUsage(generation, metricTokenTypeCacheWrite, usage.cacheWriteInputTokens);
|
|
462
|
+
this.recordTokenUsage(generation, metricTokenTypeCacheCreation, usage.cacheCreationInputTokens);
|
|
463
|
+
this.recordTokenUsage(generation, metricTokenTypeReasoning, usage.reasoningTokens);
|
|
464
|
+
}
|
|
465
|
+
this.toolCallsHistogram.record(countToolCallParts(generation.output ?? []), {
|
|
466
|
+
[spanAttrProviderName]: generation.model.provider,
|
|
467
|
+
[spanAttrRequestModel]: generation.model.name,
|
|
468
|
+
[spanAttrAgentName]: generation.agentName ?? '',
|
|
469
|
+
});
|
|
470
|
+
if (generation.operationName === 'streamText' && firstTokenAt !== undefined) {
|
|
471
|
+
const ttftSeconds = (firstTokenAt.getTime() - startedMs) / 1_000;
|
|
472
|
+
if (ttftSeconds >= 0) {
|
|
473
|
+
this.ttftHistogram.record(ttftSeconds, {
|
|
474
|
+
[spanAttrProviderName]: generation.model.provider,
|
|
475
|
+
[spanAttrRequestModel]: generation.model.name,
|
|
476
|
+
[spanAttrAgentName]: generation.agentName ?? '',
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
recordEmbeddingMetrics(seed, result, startedAt, completedAt, errorType, errorCategory) {
|
|
482
|
+
const durationSeconds = Math.max(0, (completedAt.getTime() - startedAt.getTime()) / 1_000);
|
|
483
|
+
this.operationDurationHistogram.record(durationSeconds, {
|
|
484
|
+
[spanAttrOperationName]: defaultEmbeddingOperationName,
|
|
485
|
+
[spanAttrProviderName]: seed.model.provider,
|
|
486
|
+
[spanAttrRequestModel]: seed.model.name,
|
|
487
|
+
[spanAttrAgentName]: seed.agentName ?? '',
|
|
488
|
+
[spanAttrErrorType]: errorType,
|
|
489
|
+
[spanAttrErrorCategory]: errorCategory,
|
|
490
|
+
});
|
|
491
|
+
if (result.inputTokens !== undefined && result.inputTokens !== 0) {
|
|
492
|
+
this.tokenUsageHistogram.record(result.inputTokens, {
|
|
493
|
+
[spanAttrOperationName]: defaultEmbeddingOperationName,
|
|
494
|
+
[spanAttrProviderName]: seed.model.provider,
|
|
495
|
+
[spanAttrRequestModel]: seed.model.name,
|
|
496
|
+
[spanAttrAgentName]: seed.agentName ?? '',
|
|
497
|
+
[metricAttrTokenType]: metricTokenTypeInput,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
recordTokenUsage(generation, tokenType, value) {
|
|
502
|
+
if (value === undefined || value === 0) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
this.tokenUsageHistogram.record(value, {
|
|
506
|
+
[spanAttrOperationName]: generation.operationName,
|
|
507
|
+
[spanAttrProviderName]: generation.model.provider,
|
|
508
|
+
[spanAttrRequestModel]: generation.model.name,
|
|
509
|
+
[spanAttrAgentName]: generation.agentName ?? '',
|
|
510
|
+
[metricAttrTokenType]: tokenType,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
recordToolExecutionMetrics(toolExecution, finalError) {
|
|
514
|
+
const startedMs = toolExecution.startedAt.getTime();
|
|
515
|
+
const completedMs = toolExecution.completedAt.getTime();
|
|
516
|
+
const durationSeconds = Math.max(0, (completedMs - startedMs) / 1_000);
|
|
517
|
+
const errorType = finalError === undefined ? '' : 'tool_execution_error';
|
|
518
|
+
const errorCategory = finalError === undefined ? '' : errorCategoryFromError(finalError, true);
|
|
519
|
+
this.operationDurationHistogram.record(durationSeconds, {
|
|
520
|
+
[spanAttrOperationName]: 'execute_tool',
|
|
521
|
+
[spanAttrProviderName]: (toolExecution.requestProvider ?? '').trim(),
|
|
522
|
+
[spanAttrRequestModel]: (toolExecution.requestModel ?? '').trim(),
|
|
523
|
+
[spanAttrToolName]: toolExecution.toolName.trim(),
|
|
524
|
+
[spanAttrAgentName]: toolExecution.agentName ?? '',
|
|
525
|
+
[spanAttrErrorType]: errorType,
|
|
526
|
+
[spanAttrErrorCategory]: errorCategory,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
assertOpen() {
|
|
530
|
+
if (this.shuttingDown || this.closed) {
|
|
531
|
+
throw new Error('sigil client is shutdown');
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
startGenerationWithMode(start, mode, callback) {
|
|
535
|
+
this.assertOpen();
|
|
536
|
+
const recorder = new GenerationRecorderImpl(this, start, mode);
|
|
537
|
+
if (callback === undefined) {
|
|
538
|
+
return recorder;
|
|
539
|
+
}
|
|
540
|
+
return runWithRecorder(recorder, callback);
|
|
541
|
+
}
|
|
542
|
+
triggerAsyncFlush() {
|
|
543
|
+
void this.flushInternal().catch((error) => {
|
|
544
|
+
this.logWarn('sigil generation export failed', error);
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
flushInternal() {
|
|
548
|
+
if (this.flushPromise !== undefined) {
|
|
549
|
+
this.flushRequested = true;
|
|
550
|
+
return this.flushPromise;
|
|
551
|
+
}
|
|
552
|
+
this.flushPromise = this.drainPendingGenerations().finally(() => {
|
|
553
|
+
this.flushPromise = undefined;
|
|
554
|
+
});
|
|
555
|
+
return this.flushPromise;
|
|
556
|
+
}
|
|
557
|
+
async drainPendingGenerations() {
|
|
558
|
+
do {
|
|
559
|
+
this.flushRequested = false;
|
|
560
|
+
while (this.pendingGenerations.length > 0) {
|
|
561
|
+
const batchSize = Math.max(1, this.config.generationExport.batchSize);
|
|
562
|
+
const batch = this.pendingGenerations.splice(0, batchSize).map(cloneGeneration);
|
|
563
|
+
await this.exportWithRetry(batch);
|
|
564
|
+
}
|
|
565
|
+
} while (this.flushRequested || this.pendingGenerations.length > 0);
|
|
566
|
+
}
|
|
567
|
+
async exportWithRetry(generations) {
|
|
568
|
+
const maxRetries = Math.max(0, this.config.generationExport.maxRetries);
|
|
569
|
+
const attempts = maxRetries + 1;
|
|
570
|
+
const baseBackoffMs = this.config.generationExport.initialBackoffMs > 0 ? this.config.generationExport.initialBackoffMs : 100;
|
|
571
|
+
const maxBackoffMs = this.config.generationExport.maxBackoffMs > 0 ? this.config.generationExport.maxBackoffMs : baseBackoffMs;
|
|
572
|
+
let backoffMs = baseBackoffMs;
|
|
573
|
+
let lastError;
|
|
574
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
575
|
+
try {
|
|
576
|
+
const response = await this.generationExporter.exportGenerations({
|
|
577
|
+
generations: generations.map(cloneGeneration),
|
|
578
|
+
});
|
|
579
|
+
this.logRejectedResults(response.results);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
lastError = asError(error);
|
|
584
|
+
if (attempt === attempts - 1) {
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
await this.sleepFn(backoffMs);
|
|
588
|
+
backoffMs = Math.min(backoffMs * 2, maxBackoffMs);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
throw lastError ?? new Error('generation export failed');
|
|
592
|
+
}
|
|
593
|
+
logRejectedResults(results) {
|
|
594
|
+
for (const result of results) {
|
|
595
|
+
if (!result.accepted) {
|
|
596
|
+
this.logWarn(`sigil generation rejected id=${result.generationId}`, result.error);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
stopFlushTimer() {
|
|
601
|
+
if (this.flushTimer !== undefined) {
|
|
602
|
+
clearInterval(this.flushTimer);
|
|
603
|
+
this.flushTimer = undefined;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
logWarn(message, error) {
|
|
607
|
+
if (error === undefined) {
|
|
608
|
+
this.logger.warn?.(message);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
this.logger.warn?.(`${message}: ${asError(error).message}`);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
class GenerationRecorderImpl {
|
|
615
|
+
client;
|
|
616
|
+
seed;
|
|
617
|
+
startedAt;
|
|
618
|
+
mode;
|
|
619
|
+
span;
|
|
620
|
+
contentCaptureMode;
|
|
621
|
+
ended = false;
|
|
622
|
+
result;
|
|
623
|
+
callError;
|
|
624
|
+
localError;
|
|
625
|
+
firstTokenAt;
|
|
626
|
+
constructor(client, seed, defaultMode) {
|
|
627
|
+
this.client = client;
|
|
628
|
+
this.seed = cloneGenerationStart(seed);
|
|
629
|
+
if (!notEmpty(this.seed.conversationId)) {
|
|
630
|
+
this.seed.conversationId = conversationIdFromContext();
|
|
631
|
+
}
|
|
632
|
+
if (!notEmpty(this.seed.conversationTitle)) {
|
|
633
|
+
this.seed.conversationTitle = conversationTitleFromContext();
|
|
634
|
+
}
|
|
635
|
+
if (!notEmpty(this.seed.userId)) {
|
|
636
|
+
this.seed.userId = userIdFromContext();
|
|
637
|
+
}
|
|
638
|
+
if (!notEmpty(this.seed.agentName)) {
|
|
639
|
+
this.seed.agentName = agentNameFromContext();
|
|
640
|
+
}
|
|
641
|
+
if (!notEmpty(this.seed.agentVersion)) {
|
|
642
|
+
this.seed.agentVersion = agentVersionFromContext();
|
|
643
|
+
}
|
|
644
|
+
if (!notEmpty(this.seed.operationName)) {
|
|
645
|
+
this.seed.operationName = defaultOperationNameForMode(this.seed.mode ?? defaultMode);
|
|
646
|
+
}
|
|
647
|
+
this.mode = this.seed.mode ?? defaultMode;
|
|
648
|
+
this.startedAt = this.seed.startedAt ?? this.client.internalNow();
|
|
649
|
+
this.contentCaptureMode = this.client.internalResolveGenerationContentCaptureMode(this.seed);
|
|
650
|
+
this.span = this.client.internalStartGenerationSpan(this.seed, this.mode, this.startedAt);
|
|
651
|
+
}
|
|
652
|
+
setResult(result) {
|
|
653
|
+
if (this.ended) {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
this.result = cloneGenerationResult(result);
|
|
657
|
+
}
|
|
658
|
+
setCallError(error) {
|
|
659
|
+
if (this.ended) {
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
this.callError = asError(error).message;
|
|
663
|
+
}
|
|
664
|
+
setFirstTokenAt(firstTokenAt) {
|
|
665
|
+
if (this.ended) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
if (!(firstTokenAt instanceof Date) || Number.isNaN(firstTokenAt.getTime())) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
this.firstTokenAt = new Date(firstTokenAt);
|
|
672
|
+
}
|
|
673
|
+
end() {
|
|
674
|
+
if (this.ended) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
this.ended = true;
|
|
678
|
+
const generation = {
|
|
679
|
+
id: this.seed.id ?? newLocalID('gen'),
|
|
680
|
+
conversationId: firstNonEmptyString(this.result?.conversationId, this.seed.conversationId),
|
|
681
|
+
conversationTitle: firstNonEmptyString(this.result?.conversationTitle, this.seed.conversationTitle),
|
|
682
|
+
userId: firstNonEmptyString(this.result?.userId, this.seed.userId),
|
|
683
|
+
agentName: firstNonEmptyString(this.result?.agentName, this.seed.agentName),
|
|
684
|
+
agentVersion: firstNonEmptyString(this.result?.agentVersion, this.seed.agentVersion),
|
|
685
|
+
mode: this.mode,
|
|
686
|
+
operationName: this.result?.operationName ?? this.seed.operationName ?? defaultOperationNameForMode(this.mode),
|
|
687
|
+
model: cloneModelRef(this.seed.model),
|
|
688
|
+
systemPrompt: this.seed.systemPrompt,
|
|
689
|
+
responseId: this.result?.responseId,
|
|
690
|
+
responseModel: this.result?.responseModel,
|
|
691
|
+
maxTokens: this.result?.maxTokens ?? this.seed.maxTokens,
|
|
692
|
+
temperature: this.result?.temperature ?? this.seed.temperature,
|
|
693
|
+
topP: this.result?.topP ?? this.seed.topP,
|
|
694
|
+
toolChoice: this.result?.toolChoice ?? this.seed.toolChoice,
|
|
695
|
+
thinkingEnabled: this.result?.thinkingEnabled ?? this.seed.thinkingEnabled,
|
|
696
|
+
parentGenerationIds: this.result?.parentGenerationIds?.length
|
|
697
|
+
? [...this.result.parentGenerationIds]
|
|
698
|
+
: this.seed.parentGenerationIds?.length
|
|
699
|
+
? [...this.seed.parentGenerationIds]
|
|
700
|
+
: undefined,
|
|
701
|
+
input: this.result?.input?.map(cloneMessage),
|
|
702
|
+
output: this.result?.output?.map(cloneMessage),
|
|
703
|
+
tools: this.result?.tools?.map(cloneToolDefinition) ?? this.seed.tools?.map(cloneToolDefinition),
|
|
704
|
+
usage: this.result?.usage ? { ...this.result.usage } : undefined,
|
|
705
|
+
stopReason: this.result?.stopReason,
|
|
706
|
+
startedAt: new Date(this.startedAt),
|
|
707
|
+
completedAt: new Date(this.result?.completedAt ?? this.client.internalNow()),
|
|
708
|
+
tags: mergeStringRecords(this.seed.tags, this.result?.tags),
|
|
709
|
+
metadata: mergeUnknownRecords(this.seed.metadata, this.result?.metadata),
|
|
710
|
+
artifacts: this.result?.artifacts?.map(cloneArtifact),
|
|
711
|
+
callError: this.callError,
|
|
712
|
+
};
|
|
713
|
+
generation.conversationTitle = firstNonEmptyString(generation.conversationTitle, metadataStringValue(generation.metadata, spanAttrConversationTitle))?.trim();
|
|
714
|
+
if (notEmpty(generation.conversationTitle)) {
|
|
715
|
+
if (generation.metadata === undefined) {
|
|
716
|
+
generation.metadata = {};
|
|
717
|
+
}
|
|
718
|
+
generation.metadata[spanAttrConversationTitle] = generation.conversationTitle;
|
|
719
|
+
}
|
|
720
|
+
generation.userId = firstNonEmptyString(generation.userId, metadataStringValue(generation.metadata, metadataUserIDKey), metadataStringValue(generation.metadata, metadataLegacyUserIDKey))?.trim();
|
|
721
|
+
if (notEmpty(generation.userId)) {
|
|
722
|
+
if (generation.metadata === undefined) {
|
|
723
|
+
generation.metadata = {};
|
|
724
|
+
}
|
|
725
|
+
generation.metadata[metadataUserIDKey] = generation.userId;
|
|
726
|
+
}
|
|
727
|
+
if (this.callError !== undefined) {
|
|
728
|
+
if (generation.metadata === undefined) {
|
|
729
|
+
generation.metadata = {};
|
|
730
|
+
}
|
|
731
|
+
generation.metadata.call_error = this.callError;
|
|
732
|
+
}
|
|
733
|
+
if (generation.metadata === undefined) {
|
|
734
|
+
generation.metadata = {};
|
|
735
|
+
}
|
|
736
|
+
generation.metadata[spanAttrSDKName] = sdkName;
|
|
737
|
+
const validationError = validateGeneration(generation);
|
|
738
|
+
const callErrorCategory = errorCategoryFromError(this.callError, false);
|
|
739
|
+
stampContentCaptureMetadata(generation, this.contentCaptureMode);
|
|
740
|
+
if (this.contentCaptureMode === 'metadata_only') {
|
|
741
|
+
stripContent(generation, callErrorCategory);
|
|
742
|
+
}
|
|
743
|
+
this.client.internalSyncGenerationSpan(this.span, generation);
|
|
744
|
+
if (this.contentCaptureMode === 'metadata_only') {
|
|
745
|
+
this.client.internalClearSpanConversationTitle(this.span);
|
|
746
|
+
}
|
|
747
|
+
this.client.internalApplyTraceContextFromSpan(this.span, generation);
|
|
748
|
+
this.client.internalRecordGeneration(generation);
|
|
749
|
+
let enqueueError;
|
|
750
|
+
if (validationError !== undefined) {
|
|
751
|
+
this.localError = validationError;
|
|
752
|
+
this.client.internalLogWarn('sigil generation validation failed', validationError);
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
try {
|
|
756
|
+
this.client.internalEnqueueGeneration(generation);
|
|
757
|
+
}
|
|
758
|
+
catch (error) {
|
|
759
|
+
enqueueError = asError(error);
|
|
760
|
+
this.localError = enqueueError;
|
|
761
|
+
this.client.internalLogWarn('sigil generation enqueue failed', enqueueError);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
const finalCallError = this.contentCaptureMode === 'metadata_only' ? generation.callError : this.callError;
|
|
765
|
+
this.client.internalFinalizeGenerationSpan(this.span, generation, finalCallError, validationError, enqueueError, this.firstTokenAt, callErrorCategory.length > 0 ? callErrorCategory : undefined);
|
|
766
|
+
}
|
|
767
|
+
getError() {
|
|
768
|
+
return this.localError;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
class EmbeddingRecorderImpl {
|
|
772
|
+
client;
|
|
773
|
+
seed;
|
|
774
|
+
startedAt;
|
|
775
|
+
span;
|
|
776
|
+
ended = false;
|
|
777
|
+
callError;
|
|
778
|
+
result;
|
|
779
|
+
hasResult = false;
|
|
780
|
+
localError;
|
|
781
|
+
constructor(client, seed) {
|
|
782
|
+
this.client = client;
|
|
783
|
+
this.seed = cloneEmbeddingStart(seed);
|
|
784
|
+
this.startedAt = this.seed.startedAt ?? this.client.internalNow();
|
|
785
|
+
this.span = this.client.internalStartEmbeddingSpan(this.seed, this.startedAt);
|
|
786
|
+
}
|
|
787
|
+
setCallError(error) {
|
|
788
|
+
if (this.ended) {
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
this.callError = asError(error);
|
|
792
|
+
}
|
|
793
|
+
setResult(result) {
|
|
794
|
+
if (this.ended) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
this.result = cloneEmbeddingResult(result);
|
|
798
|
+
this.hasResult = true;
|
|
799
|
+
}
|
|
800
|
+
end() {
|
|
801
|
+
if (this.ended) {
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
this.ended = true;
|
|
805
|
+
const completedAt = this.client.internalNow();
|
|
806
|
+
const normalizedResult = this.result ? cloneEmbeddingResult(this.result) : { inputCount: 0 };
|
|
807
|
+
let localError = validateEmbeddingStart(this.seed);
|
|
808
|
+
if (localError === undefined) {
|
|
809
|
+
localError = validateEmbeddingResult(normalizedResult);
|
|
810
|
+
}
|
|
811
|
+
this.client.internalFinalizeEmbeddingSpan(this.span, this.seed, normalizedResult, this.hasResult, this.callError, localError, this.startedAt, completedAt);
|
|
812
|
+
this.localError = localError;
|
|
813
|
+
}
|
|
814
|
+
getError() {
|
|
815
|
+
return this.localError;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
class ToolExecutionRecorderImpl {
|
|
819
|
+
client;
|
|
820
|
+
seed;
|
|
821
|
+
startedAt;
|
|
822
|
+
span;
|
|
823
|
+
resolvedIncludeContent;
|
|
824
|
+
ended = false;
|
|
825
|
+
result;
|
|
826
|
+
callError;
|
|
827
|
+
localError;
|
|
828
|
+
constructor(client, seed) {
|
|
829
|
+
this.client = client;
|
|
830
|
+
this.seed = cloneToolExecutionStart(seed);
|
|
831
|
+
if (!notEmpty(this.seed.conversationId)) {
|
|
832
|
+
this.seed.conversationId = conversationIdFromContext();
|
|
833
|
+
}
|
|
834
|
+
if (!notEmpty(this.seed.conversationTitle)) {
|
|
835
|
+
this.seed.conversationTitle = conversationTitleFromContext();
|
|
836
|
+
}
|
|
837
|
+
if (!notEmpty(this.seed.agentName)) {
|
|
838
|
+
this.seed.agentName = agentNameFromContext();
|
|
839
|
+
}
|
|
840
|
+
if (!notEmpty(this.seed.agentVersion)) {
|
|
841
|
+
this.seed.agentVersion = agentVersionFromContext();
|
|
842
|
+
}
|
|
843
|
+
this.resolvedIncludeContent = this.client.internalResolveToolIncludeContent(this.seed);
|
|
844
|
+
this.startedAt = this.seed.startedAt ?? this.client.internalNow();
|
|
845
|
+
this.span = this.client.internalStartToolExecutionSpan(this.seed, this.startedAt);
|
|
846
|
+
}
|
|
847
|
+
setResult(result) {
|
|
848
|
+
if (this.ended) {
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
this.result = cloneToolExecutionResult(result);
|
|
852
|
+
}
|
|
853
|
+
setCallError(error) {
|
|
854
|
+
if (this.ended) {
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
this.localError = asError(error);
|
|
858
|
+
this.callError = this.localError.message;
|
|
859
|
+
}
|
|
860
|
+
end() {
|
|
861
|
+
if (this.ended) {
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
this.ended = true;
|
|
865
|
+
const toolExecution = {
|
|
866
|
+
toolName: this.seed.toolName,
|
|
867
|
+
toolCallId: this.seed.toolCallId,
|
|
868
|
+
toolType: this.seed.toolType,
|
|
869
|
+
toolDescription: this.seed.toolDescription,
|
|
870
|
+
conversationId: this.seed.conversationId,
|
|
871
|
+
conversationTitle: this.seed.conversationTitle,
|
|
872
|
+
agentName: this.seed.agentName,
|
|
873
|
+
agentVersion: this.seed.agentVersion,
|
|
874
|
+
requestModel: this.seed.requestModel,
|
|
875
|
+
requestProvider: this.seed.requestProvider,
|
|
876
|
+
includeContent: this.resolvedIncludeContent,
|
|
877
|
+
startedAt: new Date(this.startedAt),
|
|
878
|
+
completedAt: new Date(this.result?.completedAt ?? this.client.internalNow()),
|
|
879
|
+
arguments: this.result?.arguments,
|
|
880
|
+
result: this.result?.result,
|
|
881
|
+
callError: this.callError,
|
|
882
|
+
};
|
|
883
|
+
const validationError = validateToolExecution(toolExecution);
|
|
884
|
+
if (validationError !== undefined) {
|
|
885
|
+
this.localError = validationError;
|
|
886
|
+
this.client.internalLogWarn('sigil tool execution validation failed', validationError);
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
this.client.internalRecordToolExecution(toolExecution);
|
|
890
|
+
}
|
|
891
|
+
this.localError = this.client.internalFinalizeToolExecutionSpan(this.span, toolExecution, this.localError);
|
|
892
|
+
}
|
|
893
|
+
getError() {
|
|
894
|
+
return this.localError;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
class NoopToolExecutionRecorder {
|
|
898
|
+
setResult(_result) { }
|
|
899
|
+
setCallError(_error) { }
|
|
900
|
+
end() { }
|
|
901
|
+
getError() {
|
|
902
|
+
return undefined;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
async function runWithRecorder(recorder, callback) {
|
|
906
|
+
let callbackError;
|
|
907
|
+
try {
|
|
908
|
+
return await callback(recorder);
|
|
909
|
+
}
|
|
910
|
+
catch (error) {
|
|
911
|
+
callbackError = error;
|
|
912
|
+
recorder.setCallError(error);
|
|
913
|
+
throw error;
|
|
914
|
+
}
|
|
915
|
+
finally {
|
|
916
|
+
recorder.end();
|
|
917
|
+
const recorderError = recorder.getError();
|
|
918
|
+
if (callbackError === undefined && recorderError !== undefined) {
|
|
919
|
+
// biome-ignore lint/correctness/noUnsafeFinally: intentional — only throws when callback succeeded but recorder detected an error
|
|
920
|
+
throw recorderError;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
function generationSpanName(operationName, modelName) {
|
|
925
|
+
const operation = operationName.trim();
|
|
926
|
+
const model = modelName.trim();
|
|
927
|
+
if (model.length === 0) {
|
|
928
|
+
return operation;
|
|
929
|
+
}
|
|
930
|
+
return `${operation} ${model}`;
|
|
931
|
+
}
|
|
932
|
+
function embeddingSpanName(modelName) {
|
|
933
|
+
const model = modelName.trim();
|
|
934
|
+
if (model.length === 0) {
|
|
935
|
+
return defaultEmbeddingOperationName;
|
|
936
|
+
}
|
|
937
|
+
return `${defaultEmbeddingOperationName} ${model}`;
|
|
938
|
+
}
|
|
939
|
+
function toolSpanName(toolName) {
|
|
940
|
+
const normalized = toolName.trim();
|
|
941
|
+
if (normalized.length === 0) {
|
|
942
|
+
return 'execute_tool unknown';
|
|
943
|
+
}
|
|
944
|
+
return `execute_tool ${normalized}`;
|
|
945
|
+
}
|
|
946
|
+
function setGenerationSpanAttributes(span, generation) {
|
|
947
|
+
span.setAttribute(spanAttrOperationName, generation.operationName);
|
|
948
|
+
span.setAttribute(spanAttrSDKName, sdkName);
|
|
949
|
+
if (notEmpty(generation.id)) {
|
|
950
|
+
span.setAttribute(spanAttrGenerationID, generation.id);
|
|
951
|
+
}
|
|
952
|
+
if (notEmpty(generation.conversationId)) {
|
|
953
|
+
span.setAttribute(spanAttrConversationID, generation.conversationId);
|
|
954
|
+
}
|
|
955
|
+
if (notEmpty(generation.conversationTitle)) {
|
|
956
|
+
span.setAttribute(spanAttrConversationTitle, generation.conversationTitle);
|
|
957
|
+
}
|
|
958
|
+
if (notEmpty(generation.userId)) {
|
|
959
|
+
span.setAttribute(spanAttrUserID, generation.userId);
|
|
960
|
+
}
|
|
961
|
+
if (notEmpty(generation.agentName)) {
|
|
962
|
+
span.setAttribute(spanAttrAgentName, generation.agentName);
|
|
963
|
+
}
|
|
964
|
+
if (notEmpty(generation.agentVersion)) {
|
|
965
|
+
span.setAttribute(spanAttrAgentVersion, generation.agentVersion);
|
|
966
|
+
}
|
|
967
|
+
if (notEmpty(generation.model.provider)) {
|
|
968
|
+
span.setAttribute(spanAttrProviderName, generation.model.provider);
|
|
969
|
+
}
|
|
970
|
+
if (notEmpty(generation.model.name)) {
|
|
971
|
+
span.setAttribute(spanAttrRequestModel, generation.model.name);
|
|
972
|
+
}
|
|
973
|
+
if (generation.maxTokens !== undefined) {
|
|
974
|
+
span.setAttribute(spanAttrRequestMaxTokens, generation.maxTokens);
|
|
975
|
+
}
|
|
976
|
+
if (generation.temperature !== undefined) {
|
|
977
|
+
span.setAttribute(spanAttrRequestTemperature, generation.temperature);
|
|
978
|
+
}
|
|
979
|
+
if (generation.topP !== undefined) {
|
|
980
|
+
span.setAttribute(spanAttrRequestTopP, generation.topP);
|
|
981
|
+
}
|
|
982
|
+
if (notEmpty(generation.toolChoice)) {
|
|
983
|
+
span.setAttribute(spanAttrRequestToolChoice, generation.toolChoice);
|
|
984
|
+
}
|
|
985
|
+
if (generation.thinkingEnabled !== undefined) {
|
|
986
|
+
span.setAttribute(spanAttrRequestThinkingEnabled, generation.thinkingEnabled);
|
|
987
|
+
}
|
|
988
|
+
const thinkingBudget = thinkingBudgetFromMetadata(generation.metadata);
|
|
989
|
+
if (thinkingBudget !== undefined) {
|
|
990
|
+
span.setAttribute(spanAttrRequestThinkingBudget, thinkingBudget);
|
|
991
|
+
}
|
|
992
|
+
const frameworkRunId = metadataStringValue(generation.metadata, spanAttrFrameworkRunID);
|
|
993
|
+
if (frameworkRunId !== undefined) {
|
|
994
|
+
span.setAttribute(spanAttrFrameworkRunID, frameworkRunId);
|
|
995
|
+
}
|
|
996
|
+
const frameworkThreadId = metadataStringValue(generation.metadata, spanAttrFrameworkThreadID);
|
|
997
|
+
if (frameworkThreadId !== undefined) {
|
|
998
|
+
span.setAttribute(spanAttrFrameworkThreadID, frameworkThreadId);
|
|
999
|
+
}
|
|
1000
|
+
const frameworkParentRunId = metadataStringValue(generation.metadata, spanAttrFrameworkParentRunID);
|
|
1001
|
+
if (frameworkParentRunId !== undefined) {
|
|
1002
|
+
span.setAttribute(spanAttrFrameworkParentRunID, frameworkParentRunId);
|
|
1003
|
+
}
|
|
1004
|
+
const frameworkComponentName = metadataStringValue(generation.metadata, spanAttrFrameworkComponentName);
|
|
1005
|
+
if (frameworkComponentName !== undefined) {
|
|
1006
|
+
span.setAttribute(spanAttrFrameworkComponentName, frameworkComponentName);
|
|
1007
|
+
}
|
|
1008
|
+
const frameworkRunType = metadataStringValue(generation.metadata, spanAttrFrameworkRunType);
|
|
1009
|
+
if (frameworkRunType !== undefined) {
|
|
1010
|
+
span.setAttribute(spanAttrFrameworkRunType, frameworkRunType);
|
|
1011
|
+
}
|
|
1012
|
+
const frameworkRetryAttempt = metadataIntValue(generation.metadata, spanAttrFrameworkRetryAttempt);
|
|
1013
|
+
if (frameworkRetryAttempt !== undefined) {
|
|
1014
|
+
span.setAttribute(spanAttrFrameworkRetryAttempt, frameworkRetryAttempt);
|
|
1015
|
+
}
|
|
1016
|
+
const frameworkLangGraphNode = metadataStringValue(generation.metadata, spanAttrFrameworkLangGraphNode);
|
|
1017
|
+
if (frameworkLangGraphNode !== undefined) {
|
|
1018
|
+
span.setAttribute(spanAttrFrameworkLangGraphNode, frameworkLangGraphNode);
|
|
1019
|
+
}
|
|
1020
|
+
const frameworkEventID = metadataStringValue(generation.metadata, spanAttrFrameworkEventID);
|
|
1021
|
+
if (frameworkEventID !== undefined) {
|
|
1022
|
+
span.setAttribute(spanAttrFrameworkEventID, frameworkEventID);
|
|
1023
|
+
}
|
|
1024
|
+
if (notEmpty(generation.responseId)) {
|
|
1025
|
+
span.setAttribute(spanAttrResponseID, generation.responseId);
|
|
1026
|
+
}
|
|
1027
|
+
if (notEmpty(generation.responseModel)) {
|
|
1028
|
+
span.setAttribute(spanAttrResponseModel, generation.responseModel);
|
|
1029
|
+
}
|
|
1030
|
+
if (notEmpty(generation.stopReason)) {
|
|
1031
|
+
span.setAttribute(spanAttrFinishReasons, [generation.stopReason]);
|
|
1032
|
+
}
|
|
1033
|
+
const usage = generation.usage;
|
|
1034
|
+
if (usage === undefined) {
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
if ((usage.inputTokens ?? 0) !== 0) {
|
|
1038
|
+
span.setAttribute(spanAttrInputTokens, usage.inputTokens ?? 0);
|
|
1039
|
+
}
|
|
1040
|
+
if ((usage.outputTokens ?? 0) !== 0) {
|
|
1041
|
+
span.setAttribute(spanAttrOutputTokens, usage.outputTokens ?? 0);
|
|
1042
|
+
}
|
|
1043
|
+
if ((usage.cacheReadInputTokens ?? 0) !== 0) {
|
|
1044
|
+
span.setAttribute(spanAttrCacheReadTokens, usage.cacheReadInputTokens ?? 0);
|
|
1045
|
+
}
|
|
1046
|
+
if ((usage.cacheWriteInputTokens ?? 0) !== 0) {
|
|
1047
|
+
span.setAttribute(spanAttrCacheWriteTokens, usage.cacheWriteInputTokens ?? 0);
|
|
1048
|
+
}
|
|
1049
|
+
if ((usage.cacheCreationInputTokens ?? 0) !== 0) {
|
|
1050
|
+
span.setAttribute(spanAttrCacheCreationTokens, usage.cacheCreationInputTokens ?? 0);
|
|
1051
|
+
}
|
|
1052
|
+
if ((usage.reasoningTokens ?? 0) !== 0) {
|
|
1053
|
+
span.setAttribute(spanAttrReasoningTokens, usage.reasoningTokens ?? 0);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
function setEmbeddingStartSpanAttributes(span, start) {
|
|
1057
|
+
span.setAttribute(spanAttrOperationName, defaultEmbeddingOperationName);
|
|
1058
|
+
span.setAttribute(spanAttrSDKName, sdkName);
|
|
1059
|
+
if (notEmpty(start.model.provider)) {
|
|
1060
|
+
span.setAttribute(spanAttrProviderName, start.model.provider);
|
|
1061
|
+
}
|
|
1062
|
+
if (notEmpty(start.model.name)) {
|
|
1063
|
+
span.setAttribute(spanAttrRequestModel, start.model.name);
|
|
1064
|
+
}
|
|
1065
|
+
if (notEmpty(start.agentName)) {
|
|
1066
|
+
span.setAttribute(spanAttrAgentName, start.agentName);
|
|
1067
|
+
}
|
|
1068
|
+
if (notEmpty(start.agentVersion)) {
|
|
1069
|
+
span.setAttribute(spanAttrAgentVersion, start.agentVersion);
|
|
1070
|
+
}
|
|
1071
|
+
if (start.dimensions !== undefined) {
|
|
1072
|
+
span.setAttribute(spanAttrEmbeddingDimCount, start.dimensions);
|
|
1073
|
+
}
|
|
1074
|
+
if (notEmpty(start.encodingFormat)) {
|
|
1075
|
+
span.setAttribute(spanAttrRequestEncodingFormats, [start.encodingFormat]);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
function setEmbeddingEndSpanAttributes(span, result, hasResult, captureConfig) {
|
|
1079
|
+
if (hasResult) {
|
|
1080
|
+
span.setAttribute(spanAttrEmbeddingInputCount, result.inputCount);
|
|
1081
|
+
}
|
|
1082
|
+
if (result.inputTokens !== undefined && result.inputTokens !== 0) {
|
|
1083
|
+
span.setAttribute(spanAttrInputTokens, result.inputTokens);
|
|
1084
|
+
}
|
|
1085
|
+
if (notEmpty(result.responseModel)) {
|
|
1086
|
+
span.setAttribute(spanAttrResponseModel, result.responseModel);
|
|
1087
|
+
}
|
|
1088
|
+
if (result.dimensions !== undefined) {
|
|
1089
|
+
span.setAttribute(spanAttrEmbeddingDimCount, result.dimensions);
|
|
1090
|
+
}
|
|
1091
|
+
if (captureConfig.captureInput && result.inputTexts !== undefined) {
|
|
1092
|
+
const texts = captureEmbeddingInputTexts(result.inputTexts, captureConfig.maxInputItems, captureConfig.maxTextLength);
|
|
1093
|
+
if (texts.length > 0) {
|
|
1094
|
+
span.setAttribute(spanAttrEmbeddingInputTexts, texts);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
function setToolSpanAttributes(span, tool) {
|
|
1099
|
+
span.setAttribute(spanAttrOperationName, 'execute_tool');
|
|
1100
|
+
span.setAttribute(spanAttrToolName, tool.toolName);
|
|
1101
|
+
span.setAttribute(spanAttrSDKName, sdkName);
|
|
1102
|
+
if (notEmpty(tool.toolCallId)) {
|
|
1103
|
+
span.setAttribute(spanAttrToolCallID, tool.toolCallId);
|
|
1104
|
+
}
|
|
1105
|
+
if (notEmpty(tool.toolType)) {
|
|
1106
|
+
span.setAttribute(spanAttrToolType, tool.toolType);
|
|
1107
|
+
}
|
|
1108
|
+
if (notEmpty(tool.toolDescription)) {
|
|
1109
|
+
span.setAttribute(spanAttrToolDescription, tool.toolDescription);
|
|
1110
|
+
}
|
|
1111
|
+
if (notEmpty(tool.conversationId)) {
|
|
1112
|
+
span.setAttribute(spanAttrConversationID, tool.conversationId);
|
|
1113
|
+
}
|
|
1114
|
+
if (notEmpty(tool.conversationTitle)) {
|
|
1115
|
+
span.setAttribute(spanAttrConversationTitle, tool.conversationTitle);
|
|
1116
|
+
}
|
|
1117
|
+
if (notEmpty(tool.agentName)) {
|
|
1118
|
+
span.setAttribute(spanAttrAgentName, tool.agentName);
|
|
1119
|
+
}
|
|
1120
|
+
if (notEmpty(tool.agentVersion)) {
|
|
1121
|
+
span.setAttribute(spanAttrAgentVersion, tool.agentVersion);
|
|
1122
|
+
}
|
|
1123
|
+
if (notEmpty(tool.requestProvider)) {
|
|
1124
|
+
span.setAttribute(spanAttrProviderName, tool.requestProvider);
|
|
1125
|
+
}
|
|
1126
|
+
if (notEmpty(tool.requestModel)) {
|
|
1127
|
+
span.setAttribute(spanAttrRequestModel, tool.requestModel);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
function serializeToolContent(value) {
|
|
1131
|
+
if (value === undefined || value === null) {
|
|
1132
|
+
return {};
|
|
1133
|
+
}
|
|
1134
|
+
if (typeof value === 'string') {
|
|
1135
|
+
const trimmed = value.trim();
|
|
1136
|
+
if (trimmed.length === 0) {
|
|
1137
|
+
return {};
|
|
1138
|
+
}
|
|
1139
|
+
if (isJSON(trimmed)) {
|
|
1140
|
+
return { value: trimmed };
|
|
1141
|
+
}
|
|
1142
|
+
try {
|
|
1143
|
+
return { value: JSON.stringify(trimmed) };
|
|
1144
|
+
}
|
|
1145
|
+
catch (error) {
|
|
1146
|
+
return { error: asError(error) };
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
try {
|
|
1150
|
+
const encoded = JSON.stringify(value);
|
|
1151
|
+
if (encoded === undefined || encoded === 'null') {
|
|
1152
|
+
return {};
|
|
1153
|
+
}
|
|
1154
|
+
return { value: encoded };
|
|
1155
|
+
}
|
|
1156
|
+
catch (error) {
|
|
1157
|
+
return { error: asError(error) };
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
function normalizeConversationRatingInput(input) {
|
|
1161
|
+
const normalized = {
|
|
1162
|
+
ratingId: input.ratingId.trim(),
|
|
1163
|
+
rating: input.rating.trim(),
|
|
1164
|
+
comment: input.comment?.trim(),
|
|
1165
|
+
metadata: input.metadata,
|
|
1166
|
+
generationId: input.generationId?.trim(),
|
|
1167
|
+
raterId: input.raterId?.trim(),
|
|
1168
|
+
source: input.source?.trim(),
|
|
1169
|
+
};
|
|
1170
|
+
if (normalized.ratingId.length === 0) {
|
|
1171
|
+
throw new Error('sigil conversation rating validation failed: ratingId is required');
|
|
1172
|
+
}
|
|
1173
|
+
if (normalized.ratingId.length > maxRatingIdLen) {
|
|
1174
|
+
throw new Error('sigil conversation rating validation failed: ratingId is too long');
|
|
1175
|
+
}
|
|
1176
|
+
if (normalized.rating !== 'CONVERSATION_RATING_VALUE_GOOD' && normalized.rating !== 'CONVERSATION_RATING_VALUE_BAD') {
|
|
1177
|
+
throw new Error('sigil conversation rating validation failed: rating must be CONVERSATION_RATING_VALUE_GOOD or CONVERSATION_RATING_VALUE_BAD');
|
|
1178
|
+
}
|
|
1179
|
+
if (normalized.comment !== undefined && encodedSizeBytes(normalized.comment) > maxRatingCommentBytes) {
|
|
1180
|
+
throw new Error('sigil conversation rating validation failed: comment is too long');
|
|
1181
|
+
}
|
|
1182
|
+
if (normalized.generationId !== undefined && normalized.generationId.length > maxRatingGenerationIdLen) {
|
|
1183
|
+
throw new Error('sigil conversation rating validation failed: generationId is too long');
|
|
1184
|
+
}
|
|
1185
|
+
if (normalized.raterId !== undefined && normalized.raterId.length > maxRatingActorIdLen) {
|
|
1186
|
+
throw new Error('sigil conversation rating validation failed: raterId is too long');
|
|
1187
|
+
}
|
|
1188
|
+
if (normalized.source !== undefined && normalized.source.length > maxRatingSourceLen) {
|
|
1189
|
+
throw new Error('sigil conversation rating validation failed: source is too long');
|
|
1190
|
+
}
|
|
1191
|
+
if (normalized.metadata !== undefined && encodedSizeBytes(normalized.metadata) > maxRatingMetadataBytes) {
|
|
1192
|
+
throw new Error('sigil conversation rating validation failed: metadata is too large');
|
|
1193
|
+
}
|
|
1194
|
+
return normalized;
|
|
1195
|
+
}
|
|
1196
|
+
function buildConversationRatingEndpoint(endpoint, insecure, conversationId) {
|
|
1197
|
+
const baseURL = baseURLFromAPIEndpoint(endpoint, insecure);
|
|
1198
|
+
return `${baseURL}/api/v1/conversations/${encodeURIComponent(conversationId)}/ratings`;
|
|
1199
|
+
}
|
|
1200
|
+
function baseURLFromAPIEndpoint(endpoint, insecure) {
|
|
1201
|
+
const trimmed = endpoint.trim();
|
|
1202
|
+
if (trimmed.length === 0) {
|
|
1203
|
+
throw new Error('sigil conversation rating transport failed: api endpoint is required');
|
|
1204
|
+
}
|
|
1205
|
+
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
|
|
1206
|
+
const parsed = new URL(trimmed);
|
|
1207
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
1208
|
+
}
|
|
1209
|
+
const withoutScheme = trimmed.startsWith('grpc://') ? trimmed.slice('grpc://'.length) : trimmed;
|
|
1210
|
+
const host = withoutScheme.split('/')[0]?.trim();
|
|
1211
|
+
if (host === undefined || host.length === 0) {
|
|
1212
|
+
throw new Error('sigil conversation rating transport failed: api endpoint host is required');
|
|
1213
|
+
}
|
|
1214
|
+
return `${insecure ? 'http' : 'https'}://${host}`;
|
|
1215
|
+
}
|
|
1216
|
+
function parseSubmitConversationRatingResponse(payload) {
|
|
1217
|
+
if (!isObject(payload)) {
|
|
1218
|
+
throw new Error('sigil conversation rating transport failed: invalid response payload');
|
|
1219
|
+
}
|
|
1220
|
+
if (!isObject(payload.rating) || !isObject(payload.summary)) {
|
|
1221
|
+
throw new Error('sigil conversation rating transport failed: invalid response payload');
|
|
1222
|
+
}
|
|
1223
|
+
const rating = mapConversationRating(payload.rating);
|
|
1224
|
+
const summary = mapConversationRatingSummary(payload.summary);
|
|
1225
|
+
return { rating, summary };
|
|
1226
|
+
}
|
|
1227
|
+
function mapConversationRating(payload) {
|
|
1228
|
+
const ratingId = asString(payload.rating_id);
|
|
1229
|
+
const conversationId = asString(payload.conversation_id);
|
|
1230
|
+
const rating = asString(payload.rating);
|
|
1231
|
+
const createdAt = asString(payload.created_at);
|
|
1232
|
+
if (ratingId === undefined || conversationId === undefined || rating === undefined || createdAt === undefined) {
|
|
1233
|
+
throw new Error('sigil conversation rating transport failed: invalid rating payload');
|
|
1234
|
+
}
|
|
1235
|
+
return {
|
|
1236
|
+
ratingId,
|
|
1237
|
+
conversationId,
|
|
1238
|
+
generationId: asString(payload.generation_id),
|
|
1239
|
+
rating,
|
|
1240
|
+
comment: asString(payload.comment),
|
|
1241
|
+
metadata: asRecordUnknown(payload.metadata),
|
|
1242
|
+
raterId: asString(payload.rater_id),
|
|
1243
|
+
source: asString(payload.source),
|
|
1244
|
+
createdAt,
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
function mapConversationRatingSummary(payload) {
|
|
1248
|
+
const totalCount = asNumber(payload.total_count);
|
|
1249
|
+
const goodCount = asNumber(payload.good_count);
|
|
1250
|
+
const badCount = asNumber(payload.bad_count);
|
|
1251
|
+
const latestRatedAt = asString(payload.latest_rated_at);
|
|
1252
|
+
const hasBadRating = asBoolean(payload.has_bad_rating);
|
|
1253
|
+
if (totalCount === undefined ||
|
|
1254
|
+
goodCount === undefined ||
|
|
1255
|
+
badCount === undefined ||
|
|
1256
|
+
latestRatedAt === undefined ||
|
|
1257
|
+
hasBadRating === undefined) {
|
|
1258
|
+
throw new Error('sigil conversation rating transport failed: invalid rating summary payload');
|
|
1259
|
+
}
|
|
1260
|
+
return {
|
|
1261
|
+
totalCount,
|
|
1262
|
+
goodCount,
|
|
1263
|
+
badCount,
|
|
1264
|
+
latestRating: asString(payload.latest_rating),
|
|
1265
|
+
latestRatedAt,
|
|
1266
|
+
latestBadAt: asString(payload.latest_bad_at),
|
|
1267
|
+
hasBadRating,
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
function asString(value) {
|
|
1271
|
+
return typeof value === 'string' ? value : undefined;
|
|
1272
|
+
}
|
|
1273
|
+
function asNumber(value) {
|
|
1274
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
1275
|
+
}
|
|
1276
|
+
function asBoolean(value) {
|
|
1277
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
1278
|
+
}
|
|
1279
|
+
function asRecordUnknown(value) {
|
|
1280
|
+
return isObject(value) ? value : undefined;
|
|
1281
|
+
}
|
|
1282
|
+
function isObject(value) {
|
|
1283
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
1284
|
+
}
|
|
1285
|
+
function ratingErrorText(responseText, status) {
|
|
1286
|
+
if (responseText.length > 0) {
|
|
1287
|
+
return responseText;
|
|
1288
|
+
}
|
|
1289
|
+
return `HTTP ${status}`;
|
|
1290
|
+
}
|
|
1291
|
+
function captureEmbeddingInputTexts(inputTexts, maxInputItems, maxTextLength) {
|
|
1292
|
+
if (inputTexts.length === 0) {
|
|
1293
|
+
return [];
|
|
1294
|
+
}
|
|
1295
|
+
const itemLimit = maxInputItems > 0 ? maxInputItems : 20;
|
|
1296
|
+
const textLimit = maxTextLength > 0 ? maxTextLength : 1024;
|
|
1297
|
+
const output = [];
|
|
1298
|
+
const end = Math.min(itemLimit, inputTexts.length);
|
|
1299
|
+
for (let index = 0; index < end; index++) {
|
|
1300
|
+
output.push(truncateEmbeddingText(inputTexts[index] ?? '', textLimit));
|
|
1301
|
+
}
|
|
1302
|
+
return output;
|
|
1303
|
+
}
|
|
1304
|
+
function truncateEmbeddingText(text, maxTextLength) {
|
|
1305
|
+
if (text.length <= maxTextLength) {
|
|
1306
|
+
return text;
|
|
1307
|
+
}
|
|
1308
|
+
if (maxTextLength <= 3) {
|
|
1309
|
+
return text.slice(0, maxTextLength);
|
|
1310
|
+
}
|
|
1311
|
+
return `${text.slice(0, maxTextLength - 3)}...`;
|
|
1312
|
+
}
|
|
1313
|
+
function thinkingBudgetFromMetadata(metadata) {
|
|
1314
|
+
if (metadata === undefined) {
|
|
1315
|
+
return undefined;
|
|
1316
|
+
}
|
|
1317
|
+
const raw = metadata[spanAttrRequestThinkingBudget];
|
|
1318
|
+
if (raw === undefined || raw === null || typeof raw === 'boolean') {
|
|
1319
|
+
return undefined;
|
|
1320
|
+
}
|
|
1321
|
+
if (typeof raw === 'number') {
|
|
1322
|
+
if (!Number.isFinite(raw) || !Number.isInteger(raw)) {
|
|
1323
|
+
return undefined;
|
|
1324
|
+
}
|
|
1325
|
+
return raw;
|
|
1326
|
+
}
|
|
1327
|
+
if (typeof raw === 'string') {
|
|
1328
|
+
const trimmed = raw.trim();
|
|
1329
|
+
if (trimmed.length === 0) {
|
|
1330
|
+
return undefined;
|
|
1331
|
+
}
|
|
1332
|
+
const parsed = Number.parseInt(trimmed, 10);
|
|
1333
|
+
if (Number.isNaN(parsed)) {
|
|
1334
|
+
return undefined;
|
|
1335
|
+
}
|
|
1336
|
+
return parsed;
|
|
1337
|
+
}
|
|
1338
|
+
return undefined;
|
|
1339
|
+
}
|
|
1340
|
+
function metadataStringValue(metadata, key) {
|
|
1341
|
+
if (metadata === undefined) {
|
|
1342
|
+
return undefined;
|
|
1343
|
+
}
|
|
1344
|
+
const value = metadata[key];
|
|
1345
|
+
if (typeof value !== 'string') {
|
|
1346
|
+
return undefined;
|
|
1347
|
+
}
|
|
1348
|
+
const trimmed = value.trim();
|
|
1349
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
1350
|
+
}
|
|
1351
|
+
function metadataIntValue(metadata, key) {
|
|
1352
|
+
if (metadata === undefined) {
|
|
1353
|
+
return undefined;
|
|
1354
|
+
}
|
|
1355
|
+
const value = metadata[key];
|
|
1356
|
+
if (value === undefined || value === null || typeof value === 'boolean') {
|
|
1357
|
+
return undefined;
|
|
1358
|
+
}
|
|
1359
|
+
if (typeof value === 'number') {
|
|
1360
|
+
if (!Number.isFinite(value) || !Number.isInteger(value)) {
|
|
1361
|
+
return undefined;
|
|
1362
|
+
}
|
|
1363
|
+
return value;
|
|
1364
|
+
}
|
|
1365
|
+
if (typeof value === 'string') {
|
|
1366
|
+
const trimmed = value.trim();
|
|
1367
|
+
if (trimmed.length === 0) {
|
|
1368
|
+
return undefined;
|
|
1369
|
+
}
|
|
1370
|
+
const parsed = Number.parseInt(trimmed, 10);
|
|
1371
|
+
if (Number.isNaN(parsed)) {
|
|
1372
|
+
return undefined;
|
|
1373
|
+
}
|
|
1374
|
+
return parsed;
|
|
1375
|
+
}
|
|
1376
|
+
return undefined;
|
|
1377
|
+
}
|
|
1378
|
+
function firstNonEmptyString(...values) {
|
|
1379
|
+
for (const value of values) {
|
|
1380
|
+
if (notEmpty(value)) {
|
|
1381
|
+
return value;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
return undefined;
|
|
1385
|
+
}
|
|
1386
|
+
function mergeStringRecords(left, right) {
|
|
1387
|
+
if (left === undefined && right === undefined) {
|
|
1388
|
+
return undefined;
|
|
1389
|
+
}
|
|
1390
|
+
return {
|
|
1391
|
+
...(left ?? {}),
|
|
1392
|
+
...(right ?? {}),
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
function mergeUnknownRecords(left, right) {
|
|
1396
|
+
if (left === undefined && right === undefined) {
|
|
1397
|
+
return undefined;
|
|
1398
|
+
}
|
|
1399
|
+
return {
|
|
1400
|
+
...(left ?? {}),
|
|
1401
|
+
...(right ?? {}),
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
function countToolCallParts(messages) {
|
|
1405
|
+
let total = 0;
|
|
1406
|
+
for (const message of messages) {
|
|
1407
|
+
if (message.parts === undefined) {
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
for (const part of message.parts) {
|
|
1411
|
+
if (part.type === 'tool_call') {
|
|
1412
|
+
total += 1;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
return total;
|
|
1417
|
+
}
|
|
1418
|
+
function errorCategoryFromError(error, fallbackSDK) {
|
|
1419
|
+
if (error === undefined || error === null) {
|
|
1420
|
+
return fallbackSDK ? 'sdk_error' : '';
|
|
1421
|
+
}
|
|
1422
|
+
if (typeof error === 'string') {
|
|
1423
|
+
return classifyErrorCategory(extractStatusCodeFromError(error), error, fallbackSDK);
|
|
1424
|
+
}
|
|
1425
|
+
const typed = error;
|
|
1426
|
+
const statusCode = extractStatusCodeFromObject(typed) ?? extractStatusCodeFromError(asError(error).message);
|
|
1427
|
+
const message = asError(error).message;
|
|
1428
|
+
return classifyErrorCategory(statusCode, message, fallbackSDK);
|
|
1429
|
+
}
|
|
1430
|
+
function classifyErrorCategory(statusCode, message, fallbackSDK) {
|
|
1431
|
+
const lowerMessage = message.toLowerCase();
|
|
1432
|
+
if (lowerMessage.includes('timeout') || lowerMessage.includes('deadline exceeded')) {
|
|
1433
|
+
return 'timeout';
|
|
1434
|
+
}
|
|
1435
|
+
if (statusCode === 429) {
|
|
1436
|
+
return 'rate_limit';
|
|
1437
|
+
}
|
|
1438
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
1439
|
+
return 'auth_error';
|
|
1440
|
+
}
|
|
1441
|
+
if (statusCode === 408) {
|
|
1442
|
+
return 'timeout';
|
|
1443
|
+
}
|
|
1444
|
+
if (statusCode !== undefined && statusCode >= 500 && statusCode <= 599) {
|
|
1445
|
+
return 'server_error';
|
|
1446
|
+
}
|
|
1447
|
+
if (statusCode !== undefined && statusCode >= 400 && statusCode <= 499) {
|
|
1448
|
+
return 'client_error';
|
|
1449
|
+
}
|
|
1450
|
+
return fallbackSDK ? 'sdk_error' : '';
|
|
1451
|
+
}
|
|
1452
|
+
function extractStatusCodeFromObject(error) {
|
|
1453
|
+
const direct = asStatusCode(error.status) ?? asStatusCode(error.statusCode);
|
|
1454
|
+
if (direct !== undefined) {
|
|
1455
|
+
return direct;
|
|
1456
|
+
}
|
|
1457
|
+
if (isRecord(error.response)) {
|
|
1458
|
+
return asStatusCode(error.response.status) ?? asStatusCode(error.response.statusCode);
|
|
1459
|
+
}
|
|
1460
|
+
if (isRecord(error.error)) {
|
|
1461
|
+
return asStatusCode(error.error.status) ?? asStatusCode(error.error.statusCode);
|
|
1462
|
+
}
|
|
1463
|
+
return undefined;
|
|
1464
|
+
}
|
|
1465
|
+
function extractStatusCodeFromError(message) {
|
|
1466
|
+
const matches = message.match(/\b([1-5]\d\d)\b/g);
|
|
1467
|
+
if (matches === null) {
|
|
1468
|
+
return undefined;
|
|
1469
|
+
}
|
|
1470
|
+
for (const match of matches) {
|
|
1471
|
+
const parsed = Number.parseInt(match, 10);
|
|
1472
|
+
if (!Number.isNaN(parsed) && parsed >= 100 && parsed <= 599) {
|
|
1473
|
+
return parsed;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
return undefined;
|
|
1477
|
+
}
|
|
1478
|
+
function asStatusCode(value) {
|
|
1479
|
+
if (typeof value !== 'number') {
|
|
1480
|
+
return undefined;
|
|
1481
|
+
}
|
|
1482
|
+
if (!Number.isInteger(value) || value < 100 || value > 599) {
|
|
1483
|
+
return undefined;
|
|
1484
|
+
}
|
|
1485
|
+
return value;
|
|
1486
|
+
}
|
|
1487
|
+
function isRecord(value) {
|
|
1488
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
1489
|
+
}
|
|
1490
|
+
function isJSON(value) {
|
|
1491
|
+
try {
|
|
1492
|
+
JSON.parse(value);
|
|
1493
|
+
return true;
|
|
1494
|
+
}
|
|
1495
|
+
catch {
|
|
1496
|
+
return false;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
function notEmpty(value) {
|
|
1500
|
+
return value !== undefined && value.trim().length > 0;
|
|
1501
|
+
}
|
|
1502
|
+
//# sourceMappingURL=client.js.map
|