@grafana/sigil-sdk-js 0.0.1 → 0.2.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.
@@ -0,0 +1,750 @@
1
+ import { createRequire } from 'node:module';
2
+ const frameworkName = 'strands';
3
+ const frameworkSource = 'hooks';
4
+ const frameworkLanguage = 'typescript';
5
+ const sigilPluginMarker = '__sigilStrandsPlugin';
6
+ const instrumentedAgents = new WeakSet();
7
+ export class SigilStrandsHookProvider {
8
+ client;
9
+ agentName;
10
+ agentVersion;
11
+ conversationId;
12
+ conversationTitle;
13
+ userId;
14
+ provider;
15
+ providerResolver;
16
+ captureInputs;
17
+ captureOutputs;
18
+ extraTags;
19
+ extraMetadata;
20
+ resolveConversationIdFn;
21
+ invocationRunIds = new WeakMap();
22
+ modelRunIds = new WeakMap();
23
+ modelRuns = new Map();
24
+ toolRunIds = new Map();
25
+ fallbackToolRunIds = new WeakMap();
26
+ toolRuns = new Map();
27
+ sequence = 0;
28
+ constructor(client, options = {}) {
29
+ this.client = client;
30
+ this.agentName = normalizeOptionalString(options.agentName);
31
+ this.agentVersion = normalizeOptionalString(options.agentVersion);
32
+ this.conversationId = normalizeOptionalString(options.conversationId);
33
+ this.conversationTitle = normalizeOptionalString(options.conversationTitle);
34
+ this.userId = normalizeOptionalString(options.userId);
35
+ this.provider = normalizeOptionalString(options.provider);
36
+ this.providerResolver = options.providerResolver ?? 'auto';
37
+ this.captureInputs = options.captureInputs ?? true;
38
+ this.captureOutputs = options.captureOutputs ?? true;
39
+ this.extraTags = { ...(options.extraTags ?? {}) };
40
+ this.extraMetadata = { ...(options.extraMetadata ?? {}) };
41
+ this.resolveConversationIdFn = options.resolveConversationId;
42
+ }
43
+ registerHooks(agent) {
44
+ const strands = loadStrandsRuntime();
45
+ return [
46
+ agent.addHook(strands.BeforeInvocationEvent, (event) => this.beforeInvocation(event)),
47
+ agent.addHook(strands.AfterInvocationEvent, (event) => this.afterInvocation(event)),
48
+ agent.addHook(strands.BeforeModelCallEvent, (event) => this.beforeModelCall(event)),
49
+ agent.addHook(strands.ModelStreamUpdateEvent, (event) => this.modelStreamUpdate(event)),
50
+ agent.addHook(strands.AfterModelCallEvent, (event) => this.afterModelCall(event)),
51
+ agent.addHook(strands.BeforeToolCallEvent, (event) => this.beforeToolCall(event)),
52
+ agent.addHook(strands.AfterToolCallEvent, (event) => this.afterToolCall(event)),
53
+ ];
54
+ }
55
+ beforeInvocation(event) {
56
+ const runId = this.nextRunId('invocation');
57
+ this.stackFor(this.invocationRunIds, event).push(runId);
58
+ }
59
+ afterInvocation(event) {
60
+ this.popStack(this.invocationRunIds, event);
61
+ }
62
+ beforeModelCall(event) {
63
+ const agent = read(event, 'agent');
64
+ const model = read(event, 'model') ?? read(agent, 'model');
65
+ const modelConfig = modelConfigFromModel(model);
66
+ const modelName = modelNameFrom(model, modelConfig);
67
+ const provider = this.resolveProvider(modelName, model, agent, modelConfig);
68
+ const runId = this.nextRunId('model');
69
+ this.stackFor(this.modelRunIds, event).push(runId);
70
+ const context = this.buildContext(event, agent, runId, 'chat', this.peekStack(this.invocationRunIds, event));
71
+ const start = {
72
+ conversationId: context.conversationId,
73
+ conversationTitle: this.conversationTitle,
74
+ userId: this.userId,
75
+ agentName: this.agentName ?? agentName(agent),
76
+ agentVersion: this.agentVersion,
77
+ mode: 'STREAM',
78
+ operationName: 'strands.invoke',
79
+ model: { provider, name: modelName },
80
+ systemPrompt: systemPromptText(read(agent, 'systemPrompt')),
81
+ tools: toolDefinitions(agent),
82
+ maxTokens: optionalNumber(read(modelConfig, 'maxTokens')),
83
+ temperature: optionalNumber(read(modelConfig, 'temperature')),
84
+ topP: optionalNumber(read(modelConfig, 'topP')),
85
+ toolChoice: toolChoiceString(read(modelConfig, 'toolChoice')),
86
+ tags: this.frameworkTags(),
87
+ metadata: context.metadata,
88
+ };
89
+ const recorder = this.client.startStreamingGeneration(start);
90
+ this.modelRuns.set(runId, {
91
+ recorder,
92
+ inputMessages: this.captureInputs ? mapMessages(read(agent, 'messages')) : [],
93
+ captureOutputs: this.captureOutputs,
94
+ outputChunks: [],
95
+ firstTokenRecorded: false,
96
+ });
97
+ }
98
+ modelStreamUpdate(event) {
99
+ const runId = this.peekStack(this.modelRunIds, event);
100
+ if (runId === undefined) {
101
+ return;
102
+ }
103
+ const state = this.modelRuns.get(runId);
104
+ if (state === undefined) {
105
+ return;
106
+ }
107
+ const innerEvent = read(event, 'event');
108
+ const usage = mapUsage(read(innerEvent, 'usage'));
109
+ if (usage !== undefined) {
110
+ state.usage = usage;
111
+ }
112
+ const text = textDelta(innerEvent);
113
+ if (text.length === 0) {
114
+ return;
115
+ }
116
+ if (state.captureOutputs) {
117
+ state.outputChunks.push(text);
118
+ }
119
+ if (!state.firstTokenRecorded) {
120
+ state.firstTokenRecorded = true;
121
+ state.recorder.setFirstTokenAt(new Date());
122
+ }
123
+ }
124
+ afterModelCall(event) {
125
+ const runId = this.popStack(this.modelRunIds, event);
126
+ if (runId === undefined) {
127
+ return;
128
+ }
129
+ const state = this.modelRuns.get(runId);
130
+ if (state === undefined) {
131
+ return;
132
+ }
133
+ this.modelRuns.delete(runId);
134
+ try {
135
+ const error = read(event, 'error');
136
+ if (error !== undefined) {
137
+ state.recorder.setCallError(error);
138
+ }
139
+ const stopData = read(event, 'stopData');
140
+ const message = read(stopData, 'message');
141
+ let output = this.captureOutputs ? mapMessages([message]) : undefined;
142
+ if ((output?.length ?? 0) === 0 && state.captureOutputs && state.outputChunks.length > 0) {
143
+ output = [{ role: 'assistant', content: state.outputChunks.join('') }];
144
+ }
145
+ const usage = mapUsage(read(read(message, 'metadata'), 'usage')) ?? state.usage;
146
+ state.recorder.setResult({
147
+ input: state.inputMessages,
148
+ output,
149
+ usage,
150
+ stopReason: asString(read(stopData, 'stopReason')) || undefined,
151
+ responseModel: modelNameFrom(read(event, 'model'), modelConfigFromModel(read(event, 'model'))) || undefined,
152
+ });
153
+ }
154
+ finally {
155
+ state.recorder.end();
156
+ }
157
+ const recorderError = state.recorder.getError();
158
+ if (recorderError !== undefined) {
159
+ throw recorderError;
160
+ }
161
+ }
162
+ beforeToolCall(event) {
163
+ const agent = read(event, 'agent');
164
+ const toolUse = asRecord(read(event, 'toolUse')) ?? {};
165
+ const tool = read(event, 'tool');
166
+ const toolUseId = asString(read(toolUse, 'toolUseId'));
167
+ const runId = this.nextRunId('tool');
168
+ if (toolUseId.length > 0) {
169
+ this.toolRunIds.set(this.toolKey(event, toolUseId), runId);
170
+ }
171
+ else {
172
+ this.stackFor(this.fallbackToolRunIds, event).push(runId);
173
+ }
174
+ const model = read(agent, 'model');
175
+ const modelConfig = modelConfigFromModel(model);
176
+ const context = this.buildContext(event, agent, runId, 'tool', this.peekStack(this.modelRunIds, event));
177
+ const toolName = firstNonEmpty(asString(read(toolUse, 'name')), asString(read(tool, 'name')), 'framework_tool');
178
+ const recorder = this.client.startToolExecution({
179
+ toolName,
180
+ toolCallId: toolUseId || undefined,
181
+ toolType: 'strands',
182
+ toolDescription: firstNonEmpty(asString(read(tool, 'description')), asString(read(read(tool, 'toolSpec'), 'description'))),
183
+ conversationId: context.conversationId,
184
+ conversationTitle: this.conversationTitle,
185
+ agentName: this.agentName ?? agentName(agent),
186
+ agentVersion: this.agentVersion,
187
+ requestModel: modelNameFrom(model, modelConfig),
188
+ requestProvider: this.resolveProvider(modelNameFrom(model, modelConfig), model, agent, modelConfig),
189
+ });
190
+ this.toolRuns.set(runId, {
191
+ recorder,
192
+ input: read(toolUse, 'input'),
193
+ });
194
+ }
195
+ afterToolCall(event) {
196
+ const toolUse = asRecord(read(event, 'toolUse')) ?? {};
197
+ const toolUseId = asString(read(toolUse, 'toolUseId'));
198
+ const runId = toolUseId.length > 0
199
+ ? this.toolRunIds.get(this.toolKey(event, toolUseId))
200
+ : this.popStack(this.fallbackToolRunIds, event);
201
+ if (runId === undefined) {
202
+ return;
203
+ }
204
+ if (toolUseId.length > 0) {
205
+ this.toolRunIds.delete(this.toolKey(event, toolUseId));
206
+ }
207
+ const state = this.toolRuns.get(runId);
208
+ if (state === undefined) {
209
+ return;
210
+ }
211
+ this.toolRuns.delete(runId);
212
+ try {
213
+ const error = read(event, 'error');
214
+ if (error !== undefined) {
215
+ state.recorder.setCallError(error);
216
+ }
217
+ state.recorder.setResult({
218
+ arguments: toJSONSafe(state.input),
219
+ result: toolResultToJSON(read(event, 'result')),
220
+ });
221
+ }
222
+ finally {
223
+ state.recorder.end();
224
+ }
225
+ const recorderError = state.recorder.getError();
226
+ if (recorderError !== undefined) {
227
+ throw recorderError;
228
+ }
229
+ }
230
+ nextRunId(prefix) {
231
+ this.sequence += 1;
232
+ return `${prefix}:${this.sequence}`;
233
+ }
234
+ buildContext(event, agent, runId, runType, parentRunId) {
235
+ const conversationId = this.resolveConversationId(event, agent, runId);
236
+ const metadata = normalizeMetadata({
237
+ ...this.extraMetadata,
238
+ conversation_id: conversationId,
239
+ 'sigil.framework.run_id': runId,
240
+ 'sigil.framework.run_type': runType,
241
+ 'sigil.framework.component_name': agentName(agent),
242
+ ...metadataFromAgentState(agent),
243
+ });
244
+ if (parentRunId !== undefined) {
245
+ metadata['sigil.framework.parent_run_id'] = parentRunId;
246
+ }
247
+ const eventId = firstNonEmpty(asString(read(read(event, 'toolUse'), 'toolUseId')), asString(read(event, 'id')));
248
+ if (eventId.length > 0) {
249
+ metadata['sigil.framework.event_id'] = eventId;
250
+ }
251
+ return { conversationId, metadata };
252
+ }
253
+ resolveConversationId(event, agent, runId) {
254
+ const resolved = normalizeOptionalString(this.resolveConversationIdFn?.(event, agent));
255
+ return firstNonEmpty(this.conversationId ?? '', resolved ?? '', conversationIdFromPayload(metadataFromAgentState(agent)), conversationIdFromPayload(this.extraMetadata), `sigil:framework:${frameworkName}:${runId}`);
256
+ }
257
+ resolveProvider(modelName, model, agent, modelConfig) {
258
+ if (this.provider !== undefined) {
259
+ return this.provider;
260
+ }
261
+ const configProvider = firstNonEmpty(asString(read(modelConfig, 'provider')), asString(read(model, 'provider')));
262
+ if (configProvider.length > 0) {
263
+ return normalizeProvider(configProvider);
264
+ }
265
+ if (typeof this.providerResolver === 'function') {
266
+ return normalizeProvider(this.providerResolver({ modelName, model, agent, modelConfig }) ?? '') || 'custom';
267
+ }
268
+ if (this.providerResolver === 'none') {
269
+ return '';
270
+ }
271
+ return inferProviderFromModelName(modelName);
272
+ }
273
+ frameworkTags() {
274
+ return {
275
+ ...this.extraTags,
276
+ 'sigil.framework.name': frameworkName,
277
+ 'sigil.framework.source': frameworkSource,
278
+ 'sigil.framework.language': frameworkLanguage,
279
+ };
280
+ }
281
+ stackFor(store, event) {
282
+ const source = eventSource(event);
283
+ const existing = store.get(source);
284
+ if (existing !== undefined) {
285
+ return existing;
286
+ }
287
+ const created = [];
288
+ store.set(source, created);
289
+ return created;
290
+ }
291
+ peekStack(store, event) {
292
+ const stack = store.get(eventSource(event)) ?? [];
293
+ return stack[stack.length - 1];
294
+ }
295
+ popStack(store, event) {
296
+ const source = eventSource(event);
297
+ const stack = store.get(source) ?? [];
298
+ const runId = stack.pop();
299
+ if (stack.length === 0) {
300
+ store.delete(source);
301
+ }
302
+ return runId;
303
+ }
304
+ toolKey(event, toolUseId) {
305
+ return `${sourceId(eventSource(event))}:${toolUseId}`;
306
+ }
307
+ }
308
+ export class SigilStrandsPlugin {
309
+ client;
310
+ options;
311
+ name = 'sigil-strands-plugin';
312
+ [sigilPluginMarker] = true;
313
+ constructor(client, options = {}) {
314
+ this.client = client;
315
+ this.options = options;
316
+ }
317
+ initAgent(agent) {
318
+ const target = agent;
319
+ if (instrumentedAgents.has(target)) {
320
+ return;
321
+ }
322
+ const provider = createSigilStrandsHookProvider(this.client, this.options);
323
+ provider.registerHooks(target);
324
+ instrumentedAgents.add(target);
325
+ }
326
+ }
327
+ export function createSigilStrandsHookProvider(client, options = {}) {
328
+ return new SigilStrandsHookProvider(client, options);
329
+ }
330
+ export function createSigilStrandsPlugin(client, options = {}) {
331
+ return new SigilStrandsPlugin(client, options);
332
+ }
333
+ export function withSigilStrandsHooks(configOrAgent, client, options = {}) {
334
+ const plugin = createSigilStrandsPlugin(client, options);
335
+ if (configOrAgent === undefined || !isHookableAgent(configOrAgent)) {
336
+ const config = { ...(configOrAgent ?? {}) };
337
+ const plugins = asArray(config.plugins);
338
+ if (!plugins.some(isSigilPlugin)) {
339
+ plugins.push(plugin);
340
+ }
341
+ config.plugins = plugins;
342
+ return config;
343
+ }
344
+ if (!instrumentedAgents.has(configOrAgent)) {
345
+ createSigilStrandsHookProvider(client, options).registerHooks(configOrAgent);
346
+ instrumentedAgents.add(configOrAgent);
347
+ }
348
+ return configOrAgent;
349
+ }
350
+ function mapMessages(messages) {
351
+ if (!Array.isArray(messages)) {
352
+ return [];
353
+ }
354
+ const output = [];
355
+ for (const message of messages) {
356
+ const mapped = mapMessage(message);
357
+ if (mapped !== undefined) {
358
+ output.push(mapped);
359
+ }
360
+ }
361
+ return output;
362
+ }
363
+ function mapMessage(message) {
364
+ if (message === undefined || message === null) {
365
+ return undefined;
366
+ }
367
+ const parts = [];
368
+ let containsToolResult = false;
369
+ const content = read(message, 'content');
370
+ for (const block of Array.isArray(content) ? content : [content]) {
371
+ const mappedParts = mapContentBlock(block);
372
+ for (const part of mappedParts) {
373
+ if (part.type === 'tool_result') {
374
+ containsToolResult = true;
375
+ }
376
+ parts.push(part);
377
+ }
378
+ }
379
+ if (parts.length === 0) {
380
+ return undefined;
381
+ }
382
+ if (containsToolResult) {
383
+ return { role: 'tool', parts: parts.filter((part) => part.type === 'tool_result') };
384
+ }
385
+ return {
386
+ role: normalizeRole(asString(read(message, 'role'))),
387
+ parts,
388
+ };
389
+ }
390
+ function mapContentBlock(block) {
391
+ if (typeof block === 'string') {
392
+ const text = block.trim();
393
+ return text.length > 0 ? [{ type: 'text', text }] : [];
394
+ }
395
+ const text = asString(read(block, 'text'));
396
+ if (text.length > 0) {
397
+ return [{ type: 'text', text }];
398
+ }
399
+ const reasoning = read(block, 'reasoning') ?? (asString(read(block, 'type')) === 'reasoningBlock' ? block : undefined);
400
+ const thinking = firstNonEmpty(asString(read(reasoning, 'text')), asString(read(block, 'thinking')));
401
+ if (thinking.length > 0) {
402
+ return [{ type: 'thinking', thinking }];
403
+ }
404
+ const toolUse = read(block, 'toolUse') ?? (asString(read(block, 'type')) === 'toolUseBlock' ? block : undefined);
405
+ const toolName = asString(read(toolUse, 'name'));
406
+ if (toolName.length > 0) {
407
+ return [
408
+ {
409
+ type: 'tool_call',
410
+ toolCall: {
411
+ id: asString(read(toolUse, 'toolUseId')) || undefined,
412
+ name: toolName,
413
+ inputJSON: jsonString(read(toolUse, 'input')),
414
+ },
415
+ },
416
+ ];
417
+ }
418
+ const toolResult = read(block, 'toolResult') ?? (asString(read(block, 'type')) === 'toolResultBlock' ? block : undefined);
419
+ if (toolResult !== undefined) {
420
+ return [
421
+ {
422
+ type: 'tool_result',
423
+ toolResult: {
424
+ toolCallId: asString(read(toolResult, 'toolUseId')) || undefined,
425
+ content: toolResultText(read(toolResult, 'content')),
426
+ contentJSON: jsonString(read(toolResult, 'content')),
427
+ isError: asString(read(toolResult, 'status')).toLowerCase() === 'error',
428
+ },
429
+ },
430
+ ];
431
+ }
432
+ return [];
433
+ }
434
+ function toolDefinitions(agent) {
435
+ const tools = Array.isArray(read(agent, 'tools')) ? read(agent, 'tools') : registryTools(agent);
436
+ const definitions = [];
437
+ for (const tool of tools) {
438
+ const spec = read(tool, 'toolSpec') ?? tool;
439
+ const name = firstNonEmpty(asString(read(spec, 'name')), asString(read(tool, 'name')));
440
+ if (name.length === 0) {
441
+ continue;
442
+ }
443
+ definitions.push({
444
+ name,
445
+ description: firstNonEmpty(asString(read(spec, 'description')), asString(read(tool, 'description'))) || undefined,
446
+ type: 'strands',
447
+ inputSchemaJSON: jsonString(read(spec, 'inputSchema')),
448
+ });
449
+ }
450
+ return definitions;
451
+ }
452
+ function registryTools(agent) {
453
+ const registry = read(agent, 'toolRegistry');
454
+ const list = read(registry, 'list');
455
+ if (typeof list !== 'function') {
456
+ return [];
457
+ }
458
+ try {
459
+ const tools = list.call(registry);
460
+ return Array.isArray(tools) ? tools : [];
461
+ }
462
+ catch {
463
+ return [];
464
+ }
465
+ }
466
+ function modelConfigFromModel(model) {
467
+ const getConfig = read(model, 'getConfig');
468
+ if (typeof getConfig === 'function') {
469
+ try {
470
+ return getConfig.call(model);
471
+ }
472
+ catch {
473
+ return {};
474
+ }
475
+ }
476
+ return read(model, 'config') ?? {};
477
+ }
478
+ function modelNameFrom(model, modelConfig) {
479
+ return firstNonEmpty(asString(read(modelConfig, 'modelId')), asString(read(modelConfig, 'model')), asString(read(modelConfig, 'modelName')), asString(read(model, 'modelId')), asString(read(model, 'model')), asString(read(model, 'modelName')), asString(read(model, 'name')), 'unknown');
480
+ }
481
+ function mapUsage(rawUsage) {
482
+ const usage = asRecord(rawUsage);
483
+ if (usage === undefined) {
484
+ return undefined;
485
+ }
486
+ const inputTokens = optionalNumber(read(usage, 'inputTokens')) ?? optionalNumber(read(usage, 'input_tokens'));
487
+ const outputTokens = optionalNumber(read(usage, 'outputTokens')) ?? optionalNumber(read(usage, 'output_tokens'));
488
+ const totalTokens = optionalNumber(read(usage, 'totalTokens')) ??
489
+ optionalNumber(read(usage, 'total_tokens')) ??
490
+ ((inputTokens ?? 0) + (outputTokens ?? 0) || undefined);
491
+ if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined) {
492
+ return undefined;
493
+ }
494
+ return {
495
+ inputTokens,
496
+ outputTokens,
497
+ totalTokens,
498
+ cacheReadInputTokens: optionalNumber(read(usage, 'cacheReadInputTokens')) ?? optionalNumber(read(usage, 'cache_read_input_tokens')),
499
+ cacheWriteInputTokens: optionalNumber(read(usage, 'cacheWriteInputTokens')) ?? optionalNumber(read(usage, 'cache_write_input_tokens')),
500
+ };
501
+ }
502
+ function metadataFromAgentState(agent) {
503
+ const state = read(agent, 'appState');
504
+ const getAll = read(state, 'getAll');
505
+ if (typeof getAll === 'function') {
506
+ try {
507
+ const values = getAll.call(state);
508
+ return asRecord(values) ?? {};
509
+ }
510
+ catch {
511
+ return {};
512
+ }
513
+ }
514
+ return {};
515
+ }
516
+ function conversationIdFromPayload(payload) {
517
+ return firstNonEmpty(asString(read(payload, 'conversation_id')), asString(read(payload, 'conversationId')), asString(read(payload, 'session_id')), asString(read(payload, 'sessionId')), asString(read(payload, 'group_id')), asString(read(payload, 'groupId')));
518
+ }
519
+ function systemPromptText(systemPrompt) {
520
+ if (typeof systemPrompt === 'string') {
521
+ return systemPrompt.trim() || undefined;
522
+ }
523
+ const content = read(systemPrompt, 'content');
524
+ if (typeof content === 'string') {
525
+ return content.trim() || undefined;
526
+ }
527
+ if (Array.isArray(content)) {
528
+ const text = content
529
+ .map((block) => asString(read(block, 'text')))
530
+ .filter((item) => item.length > 0)
531
+ .join('\n');
532
+ return text || undefined;
533
+ }
534
+ return undefined;
535
+ }
536
+ function textDelta(event) {
537
+ if (asString(read(event, 'type')) !== 'modelContentBlockDeltaEvent') {
538
+ return '';
539
+ }
540
+ const delta = read(event, 'delta');
541
+ if (asString(read(delta, 'type')) !== 'textDelta') {
542
+ return '';
543
+ }
544
+ return asString(read(delta, 'text'));
545
+ }
546
+ function toolResultToJSON(result) {
547
+ const safe = toJSONSafe(result);
548
+ return safe === undefined ? undefined : safe;
549
+ }
550
+ function toolResultText(content) {
551
+ if (typeof content === 'string') {
552
+ return content.trim();
553
+ }
554
+ if (!Array.isArray(content)) {
555
+ return '';
556
+ }
557
+ return content
558
+ .map((item) => {
559
+ const text = asString(read(item, 'text'));
560
+ if (text.length > 0) {
561
+ return text;
562
+ }
563
+ const json = read(item, 'json');
564
+ return json === undefined ? '' : (jsonString(json) ?? '');
565
+ })
566
+ .filter((item) => item.length > 0)
567
+ .join(' ')
568
+ .trim();
569
+ }
570
+ function toJSONSafe(value) {
571
+ if (value instanceof Error) {
572
+ return { name: value.name, message: value.message };
573
+ }
574
+ if (value === undefined) {
575
+ return undefined;
576
+ }
577
+ try {
578
+ return JSON.parse(JSON.stringify(value));
579
+ }
580
+ catch {
581
+ return String(value);
582
+ }
583
+ }
584
+ function jsonString(value) {
585
+ if (value === undefined || value === null) {
586
+ return undefined;
587
+ }
588
+ try {
589
+ return JSON.stringify(value);
590
+ }
591
+ catch {
592
+ return undefined;
593
+ }
594
+ }
595
+ function normalizeMetadata(raw) {
596
+ const metadata = {};
597
+ for (const [key, value] of Object.entries(raw)) {
598
+ if (key.trim().length === 0 || value === undefined) {
599
+ continue;
600
+ }
601
+ metadata[key] = toJSONSafe(value);
602
+ }
603
+ return metadata;
604
+ }
605
+ function agentName(agent) {
606
+ return firstNonEmpty(asString(read(agent, 'name')), asString(read(agent, 'id')), className(agent), 'strands-agent');
607
+ }
608
+ function eventSource(event) {
609
+ const agent = read(event, 'agent');
610
+ if (isRecord(agent)) {
611
+ return agent;
612
+ }
613
+ return isRecord(event) ? event : sourceBox;
614
+ }
615
+ const sourceBox = {};
616
+ const sourceIds = new WeakMap();
617
+ let sourceIdSequence = 0;
618
+ function sourceId(source) {
619
+ const existing = sourceIds.get(source);
620
+ if (existing !== undefined) {
621
+ return existing;
622
+ }
623
+ sourceIdSequence += 1;
624
+ sourceIds.set(source, sourceIdSequence);
625
+ return sourceIdSequence;
626
+ }
627
+ function toolChoiceString(value) {
628
+ if (typeof value === 'string') {
629
+ return value.trim() || undefined;
630
+ }
631
+ return jsonString(value);
632
+ }
633
+ function inferProviderFromModelName(modelName) {
634
+ const normalized = modelName.toLowerCase();
635
+ if (normalized.startsWith('gpt-') ||
636
+ normalized.startsWith('o1') ||
637
+ normalized.startsWith('o3') ||
638
+ normalized.startsWith('o4')) {
639
+ return 'openai';
640
+ }
641
+ if (normalized.includes('claude')) {
642
+ return 'anthropic';
643
+ }
644
+ if (normalized.includes('gemini')) {
645
+ return 'gemini';
646
+ }
647
+ return normalized.length > 0 && normalized !== 'unknown' ? 'custom' : '';
648
+ }
649
+ function normalizeProvider(value) {
650
+ const normalized = value.trim().toLowerCase();
651
+ if (normalized === 'openai' || normalized === 'anthropic' || normalized === 'gemini') {
652
+ return normalized;
653
+ }
654
+ return normalized.length > 0 ? 'custom' : '';
655
+ }
656
+ function normalizeRole(role) {
657
+ const normalized = role.trim().toLowerCase();
658
+ return normalized === 'assistant' || normalized === 'ai' ? 'assistant' : 'user';
659
+ }
660
+ function optionalNumber(value) {
661
+ if (typeof value === 'number' && Number.isFinite(value)) {
662
+ return value;
663
+ }
664
+ if (typeof value === 'string' && value.trim().length > 0) {
665
+ const parsed = Number(value);
666
+ return Number.isFinite(parsed) ? parsed : undefined;
667
+ }
668
+ return undefined;
669
+ }
670
+ function asArray(value) {
671
+ if (Array.isArray(value)) {
672
+ return [...value];
673
+ }
674
+ return value === undefined || value === null ? [] : [value];
675
+ }
676
+ function isSigilPlugin(value) {
677
+ return isRecord(value) && value[sigilPluginMarker] === true;
678
+ }
679
+ function isHookableAgent(value) {
680
+ return isRecord(value) && typeof value.addHook === 'function';
681
+ }
682
+ function loadStrandsRuntime() {
683
+ const errors = [];
684
+ const requireFromCwd = createRequire(`${process.cwd()}/package.json`);
685
+ try {
686
+ return validateStrandsRuntime(requireFromCwd('@strands-agents/sdk'));
687
+ }
688
+ catch (error) {
689
+ errors.push(error instanceof Error ? error.message : String(error));
690
+ }
691
+ const requireFromPackage = createRequire(import.meta.url);
692
+ try {
693
+ return validateStrandsRuntime(requireFromPackage('@strands-agents/sdk'));
694
+ }
695
+ catch (error) {
696
+ errors.push(error instanceof Error ? error.message : String(error));
697
+ }
698
+ throw new Error(`@grafana/sigil-sdk-js/strands requires @strands-agents/sdk to be installed: ${errors.join('; ')}`);
699
+ }
700
+ function validateStrandsRuntime(value) {
701
+ const runtime = asRecord(value);
702
+ const required = [
703
+ 'BeforeInvocationEvent',
704
+ 'AfterInvocationEvent',
705
+ 'BeforeModelCallEvent',
706
+ 'ModelStreamUpdateEvent',
707
+ 'AfterModelCallEvent',
708
+ 'BeforeToolCallEvent',
709
+ 'AfterToolCallEvent',
710
+ ];
711
+ if (runtime === undefined || required.some((key) => typeof runtime[key] !== 'function')) {
712
+ throw new Error('@strands-agents/sdk did not expose the expected hook event constructors');
713
+ }
714
+ return runtime;
715
+ }
716
+ function normalizeOptionalString(value) {
717
+ const normalized = asString(value);
718
+ return normalized.length > 0 ? normalized : undefined;
719
+ }
720
+ function asRecord(value) {
721
+ return isRecord(value) ? value : undefined;
722
+ }
723
+ function isRecord(value) {
724
+ return typeof value === 'object' && value !== null;
725
+ }
726
+ function read(value, key) {
727
+ if (!isRecord(value)) {
728
+ return undefined;
729
+ }
730
+ return value[key];
731
+ }
732
+ function asString(value) {
733
+ return typeof value === 'string' ? value.trim() : '';
734
+ }
735
+ function className(value) {
736
+ if (!isRecord(value)) {
737
+ return '';
738
+ }
739
+ const ctor = read(value, 'constructor');
740
+ return isRecord(ctor) ? asString(read(ctor, 'name')) : '';
741
+ }
742
+ function firstNonEmpty(...values) {
743
+ for (const value of values) {
744
+ if (value.trim().length > 0) {
745
+ return value.trim();
746
+ }
747
+ }
748
+ return '';
749
+ }
750
+ //# sourceMappingURL=index.js.map