AGENT_VERSION = ContextKey.named("sigil.agent.version");
@@ -22,6 +24,24 @@ public static Scope withConversationId(String conversationId) {
return Context.current().with(CONVERSATION_ID, emptyToBlank(conversationId)).makeCurrent();
}
+ /**
+ * Sets the conversation title in the current OTel context.
+ *
+ * Use the returned {@link Scope} in try-with-resources to restore context automatically.
+ */
+ public static Scope withConversationTitle(String conversationTitle) {
+ return Context.current().with(CONVERSATION_TITLE, emptyToBlank(conversationTitle)).makeCurrent();
+ }
+
+ /**
+ * Sets the user id in the current OTel context.
+ *
+ * Use the returned {@link Scope} in try-with-resources to restore context automatically.
+ */
+ public static Scope withUserId(String userId) {
+ return Context.current().with(USER_ID, emptyToBlank(userId)).makeCurrent();
+ }
+
/**
* Sets the agent name in the current OTel context.
*
@@ -45,6 +65,16 @@ static String conversationIdFromContext() {
return value == null ? "" : value;
}
+ static String conversationTitleFromContext() {
+ String value = Context.current().get(CONVERSATION_TITLE);
+ return value == null ? "" : value;
+ }
+
+ static String userIdFromContext() {
+ String value = Context.current().get(USER_ID);
+ return value == null ? "" : value;
+ }
+
static String agentNameFromContext() {
String value = Context.current().get(AGENT_NAME);
return value == null ? "" : value;
diff --git a/java/core/src/main/java/com/grafana/sigil/sdk/ToolExecutionStart.java b/java/core/src/main/java/com/grafana/sigil/sdk/ToolExecutionStart.java
index a1769a2..9442f24 100644
--- a/java/core/src/main/java/com/grafana/sigil/sdk/ToolExecutionStart.java
+++ b/java/core/src/main/java/com/grafana/sigil/sdk/ToolExecutionStart.java
@@ -9,8 +9,11 @@ public final class ToolExecutionStart {
private String toolType = "";
private String toolDescription = "";
private String conversationId = "";
+ private String conversationTitle = "";
private String agentName = "";
private String agentVersion = "";
+ private String requestModel = "";
+ private String requestProvider = "";
private boolean includeContent;
private Instant startedAt;
@@ -59,6 +62,15 @@ public ToolExecutionStart setConversationId(String conversationId) {
return this;
}
+ public String getConversationTitle() {
+ return conversationTitle;
+ }
+
+ public ToolExecutionStart setConversationTitle(String conversationTitle) {
+ this.conversationTitle = conversationTitle == null ? "" : conversationTitle;
+ return this;
+ }
+
public String getAgentName() {
return agentName;
}
@@ -77,6 +89,28 @@ public ToolExecutionStart setAgentVersion(String agentVersion) {
return this;
}
+ /** Returns the model that requested the tool call. */
+ public String getRequestModel() {
+ return requestModel;
+ }
+
+ /** Sets the model that requested the tool call (e.g. "gpt-5"). */
+ public ToolExecutionStart setRequestModel(String requestModel) {
+ this.requestModel = requestModel == null ? "" : requestModel;
+ return this;
+ }
+
+ /** Returns the provider that served the model. */
+ public String getRequestProvider() {
+ return requestProvider;
+ }
+
+ /** Sets the provider that served the model (e.g. "openai"). */
+ public ToolExecutionStart setRequestProvider(String requestProvider) {
+ this.requestProvider = requestProvider == null ? "" : requestProvider;
+ return this;
+ }
+
public boolean isIncludeContent() {
return includeContent;
}
@@ -102,8 +136,11 @@ public ToolExecutionStart copy() {
.setToolType(toolType)
.setToolDescription(toolDescription)
.setConversationId(conversationId)
+ .setConversationTitle(conversationTitle)
.setAgentName(agentName)
.setAgentVersion(agentVersion)
+ .setRequestModel(requestModel)
+ .setRequestProvider(requestProvider)
.setIncludeContent(includeContent)
.setStartedAt(startedAt);
}
diff --git a/java/core/src/test/java/com/grafana/sigil/sdk/ConformanceTest.java b/java/core/src/test/java/com/grafana/sigil/sdk/ConformanceTest.java
new file mode 100644
index 0000000..eb4161c
--- /dev/null
+++ b/java/core/src/test/java/com/grafana/sigil/sdk/ConformanceTest.java
@@ -0,0 +1,602 @@
+package com.grafana.sigil.sdk;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.sun.net.httpserver.HttpServer;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.stub.StreamObserver;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+import io.opentelemetry.sdk.metrics.data.MetricData;
+import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
+import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
+import io.opentelemetry.sdk.trace.SdkTracerProvider;
+import io.opentelemetry.sdk.trace.data.SpanData;
+import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import sigil.v1.GenerationIngest;
+import sigil.v1.GenerationIngestServiceGrpc;
+
+class ConformanceTest {
+ @Test
+ void syncRoundtripSemantics() throws Exception {
+ try (ConformanceEnv env = new ConformanceEnv(1)) {
+ GenerationStart start = new GenerationStart()
+ .setId("gen-roundtrip")
+ .setConversationId("conv-roundtrip")
+ .setConversationTitle("Roundtrip conversation")
+ .setUserId("user-roundtrip")
+ .setAgentName("agent-roundtrip")
+ .setAgentVersion("v-roundtrip")
+ .setModel(new ModelRef().setProvider("openai").setName("gpt-5"))
+ .setMaxTokens(256L)
+ .setTemperature(0.2)
+ .setTopP(0.9)
+ .setToolChoice("required")
+ .setThinkingEnabled(false);
+ start.getTools().add(new ToolDefinition()
+ .setName("weather")
+ .setDescription("Get weather")
+ .setType("function"));
+ start.getTags().put("tenant", "dev");
+ start.getMetadata().put("trace", "roundtrip");
+
+ GenerationRecorder recorder = env.client.startGeneration(start);
+
+ GenerationResult result = new GenerationResult()
+ .setResponseId("resp-roundtrip")
+ .setResponseModel("gpt-5-2026")
+ .setUsage(new TokenUsage()
+ .setInputTokens(12)
+ .setOutputTokens(7)
+ .setTotalTokens(19)
+ .setCacheReadInputTokens(2)
+ .setCacheWriteInputTokens(1)
+ .setCacheCreationInputTokens(3)
+ .setReasoningTokens(4))
+ .setStopReason("stop");
+ result.getTags().put("region", "eu");
+ result.getMetadata().put("result", "ok");
+ result.getInput().add(new Message()
+ .setRole(MessageRole.USER)
+ .setParts(List.of(MessagePart.text("hello"))));
+ result.getOutput().add(new Message()
+ .setRole(MessageRole.ASSISTANT)
+ .setParts(List.of(
+ MessagePart.thinking("reasoning"),
+ MessagePart.toolCall(new ToolCall()
+ .setId("call-1")
+ .setName("weather")
+ .setInputJson("{\"city\":\"Paris\"}".getBytes(StandardCharsets.UTF_8))))));
+ result.getOutput().add(new Message()
+ .setRole(MessageRole.TOOL)
+ .setParts(List.of(
+ MessagePart.toolResult(new ToolResultPart()
+ .setToolCallId("call-1")
+ .setName("weather")
+ .setContent("sunny")
+ .setContentJson("{\"temp_c\":18}".getBytes(StandardCharsets.UTF_8))))));
+ result.getArtifacts().add(new Artifact()
+ .setKind(ArtifactKind.REQUEST)
+ .setName("request")
+ .setContentType("application/json")
+ .setPayload("{\"prompt\":\"hello\"}".getBytes(StandardCharsets.UTF_8)));
+ result.getArtifacts().add(new Artifact()
+ .setKind(ArtifactKind.RESPONSE)
+ .setName("response")
+ .setContentType("application/json")
+ .setPayload("{\"text\":\"sunny\"}".getBytes(StandardCharsets.UTF_8)));
+
+ recorder.setResult(result);
+ recorder.end();
+ env.client.shutdown();
+
+ GenerationIngest.Generation generation = env.singleGeneration();
+ SpanData span = env.latestGenerationSpan();
+ List metricNames = env.metricNames();
+
+ assertThat(generation.getMode()).isEqualTo(GenerationIngest.GenerationMode.GENERATION_MODE_SYNC);
+ assertThat(generation.getOperationName()).isEqualTo("generateText");
+ assertThat(generation.getConversationId()).isEqualTo("conv-roundtrip");
+ assertThat(generation.getAgentName()).isEqualTo("agent-roundtrip");
+ assertThat(generation.getAgentVersion()).isEqualTo("v-roundtrip");
+ assertThat(generation.getTraceId()).isEqualTo(span.getTraceId());
+ assertThat(generation.getSpanId()).isEqualTo(span.getSpanId());
+ assertThat(generation.getMetadata().getFieldsMap().get("sigil.conversation.title").getStringValue())
+ .isEqualTo("Roundtrip conversation");
+ assertThat(generation.getMetadata().getFieldsMap().get("sigil.user.id").getStringValue())
+ .isEqualTo("user-roundtrip");
+ assertThat(generation.getInput(0).getParts(0).getText()).isEqualTo("hello");
+ assertThat(generation.getOutput(0).getParts(0).getThinking()).isEqualTo("reasoning");
+ assertThat(generation.getOutput(0).getParts(1).getToolCall().getName()).isEqualTo("weather");
+ assertThat(generation.getOutput(1).getParts(0).getToolResult().getContent()).isEqualTo("sunny");
+ assertThat(generation.getMaxTokens()).isEqualTo(256L);
+ assertThat(generation.getTemperature()).isEqualTo(0.2d);
+ assertThat(generation.getTopP()).isEqualTo(0.9d);
+ assertThat(generation.getToolChoice()).isEqualTo("required");
+ assertThat(generation.getThinkingEnabled()).isFalse();
+ assertThat(generation.getUsage().getInputTokens()).isEqualTo(12L);
+ assertThat(generation.getUsage().getOutputTokens()).isEqualTo(7L);
+ assertThat(generation.getUsage().getTotalTokens()).isEqualTo(19L);
+ assertThat(generation.getUsage().getCacheReadInputTokens()).isEqualTo(2L);
+ assertThat(generation.getUsage().getCacheWriteInputTokens()).isEqualTo(1L);
+ assertThat(generation.getUsage().getReasoningTokens()).isEqualTo(4L);
+ assertThat(generation.getStopReason()).isEqualTo("stop");
+ assertThat(generation.getTagsMap()).containsEntry("tenant", "dev").containsEntry("region", "eu");
+ assertThat(generation.getRawArtifactsCount()).isEqualTo(2);
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_OPERATION_NAME))).isEqualTo("generateText");
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_CONVERSATION_TITLE)))
+ .isEqualTo("Roundtrip conversation");
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_USER_ID)))
+ .isEqualTo("user-roundtrip");
+ assertThat(metricNames).contains(SigilClient.METRIC_OPERATION_DURATION, SigilClient.METRIC_TOKEN_USAGE);
+ assertThat(metricNames).doesNotContain(SigilClient.METRIC_TTFT);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("conversationTitleCases")
+ void conversationTitleSemantics(String startTitle, String contextTitle, String metadataTitle, String expected) throws Exception {
+ try (ConformanceEnv env = new ConformanceEnv(1);
+ Scope ignored = contextTitle.isEmpty() ? noopScope() : SigilContext.withConversationTitle(contextTitle)) {
+ GenerationStart start = new GenerationStart()
+ .setModel(new ModelRef().setProvider("openai").setName("gpt-5"))
+ .setConversationTitle(startTitle);
+ if (!metadataTitle.isEmpty()) {
+ start.getMetadata().put(SigilClient.SPAN_ATTR_CONVERSATION_TITLE, metadataTitle);
+ }
+
+ GenerationRecorder recorder = env.client.startGeneration(start);
+ recorder.setResult(new GenerationResult());
+ recorder.end();
+ env.client.shutdown();
+
+ GenerationIngest.Generation generation = env.singleGeneration();
+ SpanData span = env.latestGenerationSpan();
+ if (expected.isEmpty()) {
+ assertThat(generation.getMetadata().getFieldsMap()).doesNotContainKey(SigilClient.SPAN_ATTR_CONVERSATION_TITLE);
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_CONVERSATION_TITLE))).isNull();
+ return;
+ }
+
+ assertThat(generation.getMetadata().getFieldsMap().get(SigilClient.SPAN_ATTR_CONVERSATION_TITLE).getStringValue())
+ .isEqualTo(expected);
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_CONVERSATION_TITLE)))
+ .isEqualTo(expected);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("userIdCases")
+ void userIdSemantics(String startUserId, String contextUserId, String canonicalUserId, String legacyUserId, String expected)
+ throws Exception {
+ try (ConformanceEnv env = new ConformanceEnv(1);
+ Scope ignored = contextUserId.isEmpty() ? noopScope() : SigilContext.withUserId(contextUserId)) {
+ GenerationStart start = new GenerationStart()
+ .setModel(new ModelRef().setProvider("openai").setName("gpt-5"))
+ .setUserId(startUserId);
+ if (!canonicalUserId.isEmpty()) {
+ start.getMetadata().put(SigilClient.METADATA_USER_ID_KEY, canonicalUserId);
+ }
+ if (!legacyUserId.isEmpty()) {
+ start.getMetadata().put(SigilClient.METADATA_LEGACY_USER_ID_KEY, legacyUserId);
+ }
+
+ GenerationRecorder recorder = env.client.startGeneration(start);
+ recorder.setResult(new GenerationResult());
+ recorder.end();
+ env.client.shutdown();
+
+ GenerationIngest.Generation generation = env.singleGeneration();
+ SpanData span = env.latestGenerationSpan();
+ assertThat(generation.getMetadata().getFieldsMap().get(SigilClient.METADATA_USER_ID_KEY).getStringValue())
+ .isEqualTo(expected);
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_USER_ID))).isEqualTo(expected);
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("agentIdentityCases")
+ void agentIdentitySemantics(
+ String startName,
+ String startVersion,
+ String contextName,
+ String contextVersion,
+ String resultName,
+ String resultVersion,
+ String expectedName,
+ String expectedVersion)
+ throws Exception {
+ try (ConformanceEnv env = new ConformanceEnv(1);
+ Scope ignoredName = contextName.isEmpty() ? noopScope() : SigilContext.withAgentName(contextName);
+ Scope ignoredVersion = contextVersion.isEmpty() ? noopScope() : SigilContext.withAgentVersion(contextVersion)) {
+ GenerationRecorder recorder = env.client.startGeneration(new GenerationStart()
+ .setModel(new ModelRef().setProvider("openai").setName("gpt-5"))
+ .setAgentName(startName)
+ .setAgentVersion(startVersion));
+ recorder.setResult(new GenerationResult()
+ .setAgentName(resultName)
+ .setAgentVersion(resultVersion));
+ recorder.end();
+ env.client.shutdown();
+
+ GenerationIngest.Generation generation = env.singleGeneration();
+ SpanData span = env.latestGenerationSpan();
+ assertThat(generation.getAgentName()).isEqualTo(expectedName);
+ assertThat(generation.getAgentVersion()).isEqualTo(expectedVersion);
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_AGENT_NAME)))
+ .isEqualTo(expectedName.isEmpty() ? null : expectedName);
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_AGENT_VERSION)))
+ .isEqualTo(expectedVersion.isEmpty() ? null : expectedVersion);
+ }
+ }
+
+ @Test
+ void streamingTelemetrySemantics() throws Exception {
+ try (ConformanceEnv env = new ConformanceEnv(1)) {
+ Instant startedAt = Instant.parse("2026-03-12T09:00:00Z");
+ GenerationRecorder recorder = env.client.startStreamingGeneration(new GenerationStart()
+ .setModel(new ModelRef().setProvider("openai").setName("gpt-5"))
+ .setStartedAt(startedAt));
+ recorder.setFirstTokenAt(startedAt.plusMillis(250));
+ recorder.setResult(new GenerationResult()
+ .setStartedAt(startedAt)
+ .setCompletedAt(startedAt.plusSeconds(1))
+ .setUsage(new TokenUsage().setInputTokens(4).setOutputTokens(3).setTotalTokens(7)));
+ recorder.end();
+ env.client.shutdown();
+
+ GenerationIngest.Generation generation = env.singleGeneration();
+ SpanData span = env.latestGenerationSpan();
+ List metricNames = env.metricNames();
+
+ assertThat(generation.getMode()).isEqualTo(GenerationIngest.GenerationMode.GENERATION_MODE_STREAM);
+ assertThat(generation.getOperationName()).isEqualTo("streamText");
+ assertThat(span.getName()).isEqualTo("streamText gpt-5");
+ assertThat(metricNames).contains(SigilClient.METRIC_OPERATION_DURATION, SigilClient.METRIC_TTFT);
+ }
+ }
+
+ @Test
+ void toolExecutionSemantics() throws Exception {
+ try (ConformanceEnv env = new ConformanceEnv(1);
+ Scope ignoredTitle = SigilContext.withConversationTitle("Context title");
+ Scope ignoredName = SigilContext.withAgentName("agent-context");
+ Scope ignoredVersion = SigilContext.withAgentVersion("v-context")) {
+ ToolExecutionRecorder recorder = env.client.startToolExecution(new ToolExecutionStart()
+ .setToolName("weather")
+ .setToolCallId("call-weather-1")
+ .setToolType("function")
+ .setIncludeContent(true));
+ recorder.setResult(new ToolExecutionResult()
+ .setArguments(Map.of("city", "Paris"))
+ .setResult(Map.of("forecast", "sunny")));
+ recorder.end();
+ env.client.shutdown();
+
+ SpanData span = env.latestSpanByNamePrefix("execute_tool ");
+ List metricNames = env.metricNames();
+
+ assertThat(env.requests).isEmpty();
+ assertThat(span.getName()).isEqualTo("execute_tool weather");
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_TOOL_NAME))).isEqualTo("weather");
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_TOOL_CALL_ID))).isEqualTo("call-weather-1");
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_TOOL_TYPE))).isEqualTo("function");
+ assertThat(String.valueOf(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_TOOL_CALL_ARGUMENTS))))
+ .contains("Paris");
+ assertThat(String.valueOf(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_TOOL_CALL_RESULT))))
+ .contains("sunny");
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_CONVERSATION_TITLE)))
+ .isEqualTo("Context title");
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_AGENT_NAME)))
+ .isEqualTo("agent-context");
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_AGENT_VERSION)))
+ .isEqualTo("v-context");
+ assertThat(metricNames).contains(SigilClient.METRIC_OPERATION_DURATION);
+ assertThat(metricNames).doesNotContain(SigilClient.METRIC_TTFT);
+ }
+ }
+
+ @Test
+ void embeddingSemantics() throws Exception {
+ try (ConformanceEnv env = new ConformanceEnv(1);
+ Scope ignoredName = SigilContext.withAgentName("agent-context");
+ Scope ignoredVersion = SigilContext.withAgentVersion("v-context")) {
+ EmbeddingRecorder recorder = env.client.startEmbedding(new EmbeddingStart()
+ .setModel(new ModelRef().setProvider("openai").setName("text-embedding-3-small"))
+ .setDimensions(512L));
+ recorder.setResult(new EmbeddingResult()
+ .setInputCount(2)
+ .setInputTokens(8)
+ .setInputTexts(List.of("hello", "world"))
+ .setResponseModel("text-embedding-3-small")
+ .setDimensions(512L));
+ recorder.end();
+ env.client.shutdown();
+
+ SpanData span = env.latestSpan("embeddings");
+ List metricNames = env.metricNames();
+
+ assertThat(env.requests).isEmpty();
+ assertThat(span.getName()).isEqualTo("embeddings text-embedding-3-small");
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_OPERATION_NAME))).isEqualTo("embeddings");
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_AGENT_NAME)))
+ .isEqualTo("agent-context");
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_AGENT_VERSION)))
+ .isEqualTo("v-context");
+ assertThat(span.getAttributes().get(AttributeKey.longKey("gen_ai.embeddings.input_count"))).isEqualTo(2L);
+ assertThat(span.getAttributes().get(AttributeKey.longKey("gen_ai.embeddings.dimension.count"))).isEqualTo(512L);
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_RESPONSE_MODEL)))
+ .isEqualTo("text-embedding-3-small");
+ assertThat(metricNames).contains(SigilClient.METRIC_OPERATION_DURATION, SigilClient.METRIC_TOKEN_USAGE);
+ assertThat(metricNames).doesNotContain(SigilClient.METRIC_TTFT, SigilClient.METRIC_TOOL_CALLS_PER_OPERATION);
+ }
+ }
+
+ @Test
+ void validationAndCallErrorSemantics() throws Exception {
+ try (ConformanceEnv env = new ConformanceEnv(1)) {
+ GenerationRecorder invalid = env.client.startGeneration(new GenerationStart()
+ .setModel(new ModelRef().setProvider("anthropic").setName("claude-sonnet-4-5")));
+ invalid.setResult(new GenerationResult().setInput(List.of(new Message()
+ .setRole(MessageRole.USER)
+ .setParts(List.of(MessagePart.toolCall(new ToolCall().setName("weather")))))));
+ invalid.end();
+
+ assertThat(invalid.error()).isPresent();
+ assertThat(env.requests).isEmpty();
+ assertThat(env.latestGenerationSpan().getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_ERROR_TYPE)))
+ .isEqualTo("validation_error");
+
+ GenerationRecorder callError = env.client.startGeneration(new GenerationStart()
+ .setModel(new ModelRef().setProvider("openai").setName("gpt-5")));
+ callError.setCallError(new IllegalStateException("provider unavailable"));
+ callError.setResult(new GenerationResult());
+ callError.end();
+ env.client.shutdown();
+
+ GenerationIngest.Generation generation = env.singleGeneration();
+ SpanData span = env.latestGenerationSpan();
+ assertThat(callError.error()).isEmpty();
+ assertThat(generation.getCallError()).isEqualTo("provider unavailable");
+ assertThat(generation.getMetadata().getFieldsMap().get("call_error").getStringValue()).isEqualTo("provider unavailable");
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_ERROR_TYPE)))
+ .isEqualTo("provider_call_error");
+ }
+ }
+
+ @Test
+ void ratingSubmissionSemantics() throws Exception {
+ try (ConformanceEnv env = new ConformanceEnv(1)) {
+ SubmitConversationRatingResponse response = env.client.submitConversationRating(
+ "conv-rating",
+ new SubmitConversationRatingRequest()
+ .setRatingId("rat-1")
+ .setRating(ConversationRatingValue.BAD)
+ .setComment("wrong answer")
+ .setMetadata(Map.of("channel", "assistant")));
+
+ assertThat(env.ratingPath.get()).isEqualTo("/api/v1/conversations/conv-rating/ratings");
+ assertThat(response.getRating().getConversationId()).isEqualTo("conv-rating");
+ assertThat(response.getSummary().getBadCount()).isEqualTo(1L);
+
+ JsonNode body = env.ratingPayload.get();
+ assertThat(body.get("rating_id").asText()).isEqualTo("rat-1");
+ assertThat(body.get("rating").asText()).isEqualTo("CONVERSATION_RATING_VALUE_BAD");
+ assertThat(body.get("comment").asText()).isEqualTo("wrong answer");
+ }
+ }
+
+ @Test
+ void shutdownFlushSemantics() throws Exception {
+ try (ConformanceEnv env = new ConformanceEnv(10)) {
+ GenerationRecorder recorder = env.client.startGeneration(new GenerationStart()
+ .setConversationId("conv-shutdown")
+ .setAgentName("agent-shutdown")
+ .setAgentVersion("v-shutdown")
+ .setModel(new ModelRef().setProvider("openai").setName("gpt-5")));
+ recorder.setResult(new GenerationResult());
+ recorder.end();
+
+ assertThat(env.requests).isEmpty();
+ env.client.shutdown();
+
+ GenerationIngest.Generation generation = env.singleGeneration();
+ assertThat(generation.getConversationId()).isEqualTo("conv-shutdown");
+ assertThat(generation.getAgentName()).isEqualTo("agent-shutdown");
+ assertThat(generation.getAgentVersion()).isEqualTo("v-shutdown");
+ }
+ }
+
+ private static Stream conversationTitleCases() {
+ return Stream.of(
+ Arguments.of("Explicit", "Context", "Meta", "Explicit"),
+ Arguments.of("", "Context", "", "Context"),
+ Arguments.of("", "", "Meta", "Meta"),
+ Arguments.of(" Padded ", "", "", "Padded"),
+ Arguments.of(" ", "", "", ""));
+ }
+
+ private static Stream userIdCases() {
+ return Stream.of(
+ Arguments.of("explicit", "ctx", "canonical", "legacy", "explicit"),
+ Arguments.of("", "ctx", "", "", "ctx"),
+ Arguments.of("", "", "canonical", "", "canonical"),
+ Arguments.of("", "", "", "legacy", "legacy"),
+ Arguments.of("", "", "canonical", "legacy", "canonical"),
+ Arguments.of(" padded ", "", "", "", "padded"));
+ }
+
+ private static Stream agentIdentityCases() {
+ return Stream.of(
+ Arguments.of("agent-explicit", "v1.2.3", "", "", "", "", "agent-explicit", "v1.2.3"),
+ Arguments.of("", "", "agent-context", "v-context", "", "", "agent-context", "v-context"),
+ Arguments.of("agent-seed", "v-seed", "", "", "agent-result", "v-result", "agent-result", "v-result"),
+ Arguments.of("", "", "", "", "", "", "", ""));
+ }
+
+ private static Scope noopScope() {
+ return () -> {
+ };
+ }
+
+ private static final class ConformanceEnv implements AutoCloseable {
+ private final Server server;
+ private final HttpServer ratingServer;
+ private final InMemorySpanExporter spanExporter = InMemorySpanExporter.create();
+ private final SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
+ .addSpanProcessor(SimpleSpanProcessor.create(spanExporter))
+ .build();
+ private final InMemoryMetricReader metricReader = InMemoryMetricReader.create();
+ private final SdkMeterProvider meterProvider = SdkMeterProvider.builder()
+ .registerMetricReader(metricReader)
+ .build();
+ private final AtomicReference ratingPath = new AtomicReference<>();
+ private final AtomicReference ratingPayload = new AtomicReference<>();
+ private final List requests = new CopyOnWriteArrayList<>();
+ private boolean closed;
+
+ private final SigilClient client;
+
+ ConformanceEnv(int batchSize) throws Exception {
+ GenerationIngestServiceGrpc.GenerationIngestServiceImplBase service =
+ new GenerationIngestServiceGrpc.GenerationIngestServiceImplBase() {
+ @Override
+ public void exportGenerations(
+ GenerationIngest.ExportGenerationsRequest request,
+ StreamObserver responseObserver) {
+ requests.add(request);
+ List results = new ArrayList<>();
+ for (GenerationIngest.Generation generation : request.getGenerationsList()) {
+ results.add(GenerationIngest.ExportGenerationResult.newBuilder()
+ .setGenerationId(generation.getId())
+ .setAccepted(true)
+ .build());
+ }
+ responseObserver.onNext(GenerationIngest.ExportGenerationsResponse.newBuilder()
+ .addAllResults(results)
+ .build());
+ responseObserver.onCompleted();
+ }
+ };
+ server = ServerBuilder.forPort(0).addService(service).build().start();
+
+ ratingServer = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
+ ratingServer.createContext("/api/v1/conversations/conv-rating/ratings", exchange -> {
+ ratingPath.set(exchange.getRequestURI().getPath());
+ ratingPayload.set(Json.MAPPER.readTree(exchange.getRequestBody().readAllBytes()));
+
+ byte[] response = """
+ {
+ "rating":{
+ "rating_id":"rat-1",
+ "conversation_id":"conv-rating",
+ "rating":"CONVERSATION_RATING_VALUE_BAD",
+ "created_at":"2026-03-12T09:00:00Z"
+ },
+ "summary":{
+ "total_count":1,
+ "good_count":0,
+ "bad_count":1,
+ "latest_rating":"CONVERSATION_RATING_VALUE_BAD",
+ "latest_rated_at":"2026-03-12T09:00:00Z",
+ "has_bad_rating":true
+ }
+ }
+ """.getBytes(StandardCharsets.UTF_8);
+ exchange.getResponseHeaders().add("Content-Type", "application/json");
+ exchange.sendResponseHeaders(200, response.length);
+ try (OutputStream outputStream = exchange.getResponseBody()) {
+ outputStream.write(response);
+ }
+ });
+ ratingServer.start();
+
+ client = new SigilClient(new SigilClientConfig()
+ .setTracer(tracerProvider.get("sigil-conformance-test"))
+ .setMeter(meterProvider.get("sigil-conformance-test"))
+ .setApi(new ApiConfig().setEndpoint("http://127.0.0.1:" + ratingServer.getAddress().getPort()))
+ .setGenerationExport(new GenerationExportConfig()
+ .setProtocol(GenerationExportProtocol.GRPC)
+ .setEndpoint("127.0.0.1:" + server.getPort())
+ .setInsecure(true)
+ .setBatchSize(batchSize)
+ .setQueueSize(10)
+ .setFlushInterval(Duration.ofHours(1))
+ .setMaxRetries(1)
+ .setInitialBackoff(Duration.ofMillis(1))
+ .setMaxBackoff(Duration.ofMillis(2))));
+ }
+
+ GenerationIngest.Generation singleGeneration() {
+ assertThat(requests).hasSize(1);
+ assertThat(requests.get(0).getGenerationsCount()).isEqualTo(1);
+ return requests.get(0).getGenerations(0);
+ }
+
+ SpanData latestGenerationSpan() {
+ List spans = spanExporter.getFinishedSpanItems().stream()
+ .filter(span -> {
+ String operation = span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_OPERATION_NAME));
+ return "generateText".equals(operation) || "streamText".equals(operation);
+ })
+ .toList();
+ assertThat(spans).isNotEmpty();
+ return spans.get(spans.size() - 1);
+ }
+
+ SpanData latestSpan(String operationName) {
+ List spans = spanExporter.getFinishedSpanItems().stream()
+ .filter(span -> operationName.equals(
+ span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_OPERATION_NAME))))
+ .toList();
+ assertThat(spans).isNotEmpty();
+ return spans.get(spans.size() - 1);
+ }
+
+ SpanData latestSpanByNamePrefix(String prefix) {
+ List spans = spanExporter.getFinishedSpanItems().stream()
+ .filter(span -> span.getName().startsWith(prefix))
+ .toList();
+ assertThat(spans).isNotEmpty();
+ return spans.get(spans.size() - 1);
+ }
+
+ List metricNames() {
+ return metricReader.collectAllMetrics().stream()
+ .map(MetricData::getName)
+ .toList();
+ }
+
+ @Override
+ public void close() {
+ if (closed) {
+ return;
+ }
+ closed = true;
+ client.shutdown();
+ server.shutdownNow();
+ ratingServer.stop(0);
+ tracerProvider.shutdown();
+ meterProvider.shutdown();
+ }
+ }
+}
diff --git a/java/core/src/test/java/com/grafana/sigil/sdk/SigilAuthConfigTest.java b/java/core/src/test/java/com/grafana/sigil/sdk/SigilAuthConfigTest.java
index 39b0aac..6c26a18 100644
--- a/java/core/src/test/java/com/grafana/sigil/sdk/SigilAuthConfigTest.java
+++ b/java/core/src/test/java/com/grafana/sigil/sdk/SigilAuthConfigTest.java
@@ -3,6 +3,9 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
@@ -13,6 +16,14 @@ void validatesAuthModeShape() {
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("mode 'none'");
+ assertThatThrownBy(() -> AuthHeaders.resolve(Map.of(), new AuthConfig().setMode(AuthMode.NONE).setBasicUser("user"), "trace"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("mode 'none'");
+
+ assertThatThrownBy(() -> AuthHeaders.resolve(Map.of(), new AuthConfig().setMode(AuthMode.NONE).setBasicPassword("secret"), "trace"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("mode 'none'");
+
assertThatThrownBy(() -> AuthHeaders.resolve(Map.of(), new AuthConfig().setMode(AuthMode.TENANT), "generation export"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("requires tenantId");
@@ -36,4 +47,69 @@ void explicitHeadersOverrideInjectedAuthHeaders() {
"generation export");
assertThat(generation.get("x-scope-orgid")).isEqualTo("tenant-override");
}
+
+ @Test
+ void basicAuthWithTenantId() {
+ Map headers = AuthHeaders.resolve(
+ Map.of(),
+ new AuthConfig().setMode(AuthMode.BASIC).setTenantId("42").setBasicPassword("secret"),
+ "generation export");
+ String expected = "Basic " + Base64.getEncoder()
+ .encodeToString("42:secret".getBytes(StandardCharsets.UTF_8));
+ assertThat(headers.get("Authorization")).isEqualTo(expected);
+ assertThat(headers.get("X-Scope-OrgID")).isEqualTo("42");
+ }
+
+ @Test
+ void basicAuthWithExplicitUser() {
+ Map headers = AuthHeaders.resolve(
+ Map.of(),
+ new AuthConfig().setMode(AuthMode.BASIC).setTenantId("42")
+ .setBasicUser("probe-user").setBasicPassword("secret"),
+ "generation export");
+ String expected = "Basic " + Base64.getEncoder()
+ .encodeToString("probe-user:secret".getBytes(StandardCharsets.UTF_8));
+ assertThat(headers.get("Authorization")).isEqualTo(expected);
+ assertThat(headers.get("X-Scope-OrgID")).isEqualTo("42");
+ }
+
+ @Test
+ void basicAuthExplicitHeaderWins() {
+ Map input = new LinkedHashMap<>();
+ input.put("Authorization", "Basic override");
+ input.put("X-Scope-OrgID", "override-tenant");
+ Map headers = AuthHeaders.resolve(
+ input,
+ new AuthConfig().setMode(AuthMode.BASIC).setTenantId("42").setBasicPassword("secret"),
+ "generation export");
+ assertThat(headers.get("Authorization")).isEqualTo("Basic override");
+ assertThat(headers.get("X-Scope-OrgID")).isEqualTo("override-tenant");
+ }
+
+ @Test
+ void basicAuthRejectsInvalidConfig() {
+ assertThatThrownBy(() -> AuthHeaders.resolve(Map.of(),
+ new AuthConfig().setMode(AuthMode.BASIC), "generation export"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("requires basicPassword");
+
+ assertThatThrownBy(() -> AuthHeaders.resolve(Map.of(),
+ new AuthConfig().setMode(AuthMode.BASIC).setBasicPassword("secret"), "generation export"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("requires basicUser or tenantId");
+ }
+
+ @Test
+ void basicAuthCopy() {
+ AuthConfig original = new AuthConfig()
+ .setMode(AuthMode.BASIC)
+ .setTenantId("42")
+ .setBasicUser("user")
+ .setBasicPassword("pass");
+ AuthConfig copy = original.copy();
+ assertThat(copy.getMode()).isEqualTo(AuthMode.BASIC);
+ assertThat(copy.getTenantId()).isEqualTo("42");
+ assertThat(copy.getBasicUser()).isEqualTo("user");
+ assertThat(copy.getBasicPassword()).isEqualTo("pass");
+ }
}
diff --git a/java/core/src/test/java/com/grafana/sigil/sdk/SigilClientSpansTest.java b/java/core/src/test/java/com/grafana/sigil/sdk/SigilClientSpansTest.java
index 2c16f96..e8bc6e7 100644
--- a/java/core/src/test/java/com/grafana/sigil/sdk/SigilClientSpansTest.java
+++ b/java/core/src/test/java/com/grafana/sigil/sdk/SigilClientSpansTest.java
@@ -85,7 +85,9 @@ void toolSpanNameAndAttributesMatchContract() {
.setToolName("weather")
.setToolCallId("call-1")
.setToolType("function")
- .setToolDescription("Get weather"));
+ .setToolDescription("Get weather")
+ .setRequestProvider("openai")
+ .setRequestModel("gpt-5"));
recorder.setResult(new ToolExecutionResult().setArguments(java.util.Map.of("city", "Paris")).setResult("18C"));
recorder.end();
}
@@ -97,6 +99,8 @@ void toolSpanNameAndAttributesMatchContract() {
assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_SDK_NAME))).isEqualTo("sdk-java");
assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_TOOL_NAME))).isEqualTo("weather");
assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_TOOL_CALL_ID))).isEqualTo("call-1");
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_PROVIDER_NAME))).isEqualTo("openai");
+ assertThat(span.getAttributes().get(AttributeKey.stringKey(SigilClient.SPAN_ATTR_REQUEST_MODEL))).isEqualTo("gpt-5");
provider.shutdown();
}
diff --git a/java/frameworks/google-adk/README.md b/java/frameworks/google-adk/README.md
index 24fd310..1ac7741 100644
--- a/java/frameworks/google-adk/README.md
+++ b/java/frameworks/google-adk/README.md
@@ -8,6 +8,7 @@ This module maps Google ADK callback/interceptor lifecycles to Sigil generation
- Optional lineage metadata (`run_id`, `thread_id`, `parent_run_id`, `event_id`)
- SYNC and STREAM lifecycle support
- Tool lifecycle support
+- Explicit embeddings unsupported contract via `SigilGoogleAdkAdapter.checkEmbeddingsSupport()`
## Install
diff --git a/java/frameworks/google-adk/build.gradle.kts b/java/frameworks/google-adk/build.gradle.kts
index b465e77..4804368 100644
--- a/java/frameworks/google-adk/build.gradle.kts
+++ b/java/frameworks/google-adk/build.gradle.kts
@@ -9,4 +9,7 @@ dependencies {
testImplementation(libs.junit.jupiter)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testImplementation(libs.assertj.core)
+ testImplementation(libs.otel.sdk.trace)
+ testImplementation(libs.otel.sdk.metrics)
+ testImplementation(libs.otel.sdk.testing)
}
diff --git a/java/frameworks/google-adk/src/main/java/com/grafana/sigil/sdk/frameworks/googleadk/SigilGoogleAdkAdapter.java b/java/frameworks/google-adk/src/main/java/com/grafana/sigil/sdk/frameworks/googleadk/SigilGoogleAdkAdapter.java
index a4516ed..8f934b8 100644
--- a/java/frameworks/google-adk/src/main/java/com/grafana/sigil/sdk/frameworks/googleadk/SigilGoogleAdkAdapter.java
+++ b/java/frameworks/google-adk/src/main/java/com/grafana/sigil/sdk/frameworks/googleadk/SigilGoogleAdkAdapter.java
@@ -29,6 +29,8 @@ public final class SigilGoogleAdkAdapter {
private static final String FRAMEWORK_SOURCE = "handler";
private static final String FRAMEWORK_LANGUAGE = "java";
private static final int MAX_METADATA_DEPTH = 5;
+ private static final String EMBEDDINGS_UNSUPPORTED_MESSAGE =
+ "google-adk: embeddings are not supported because the Google ADK lifecycle surface does not expose a dedicated embeddings callback";
static final String META_RUN_ID = "sigil.framework.run_id";
static final String META_THREAD_ID = "sigil.framework.thread_id";
@@ -84,6 +86,15 @@ public static Callbacks createCallbacks(SigilClient client, Options options) {
return new SigilGoogleAdkAdapter(client, options).callbacks();
}
+ /**
+ * Reports whether this adapter can observe a native Google ADK embeddings lifecycle.
+ * The current lifecycle surface only exposes run and tool callbacks, so embeddings
+ * remain unsupported until ADK exposes a dedicated embeddings callback.
+ */
+ public static void checkEmbeddingsSupport() {
+ throw new UnsupportedOperationException(EMBEDDINGS_UNSUPPORTED_MESSAGE);
+ }
+
public void onRunStart(RunStartEvent event) {
if (event == null || event.getRunId().isBlank()) {
return;
diff --git a/java/frameworks/google-adk/src/test/java/com/grafana/sigil/sdk/frameworks/googleadk/GoogleAdkConformanceTest.java b/java/frameworks/google-adk/src/test/java/com/grafana/sigil/sdk/frameworks/googleadk/GoogleAdkConformanceTest.java
new file mode 100644
index 0000000..61438af
--- /dev/null
+++ b/java/frameworks/google-adk/src/test/java/com/grafana/sigil/sdk/frameworks/googleadk/GoogleAdkConformanceTest.java
@@ -0,0 +1,220 @@
+package com.grafana.sigil.sdk.frameworks.googleadk;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import com.grafana.sigil.sdk.ExportGenerationResult;
+import com.grafana.sigil.sdk.ExportGenerationsRequest;
+import com.grafana.sigil.sdk.ExportGenerationsResponse;
+import com.grafana.sigil.sdk.Generation;
+import com.grafana.sigil.sdk.GenerationExportConfig;
+import com.grafana.sigil.sdk.GenerationExporter;
+import com.grafana.sigil.sdk.MessagePart;
+import com.grafana.sigil.sdk.MessageRole;
+import com.grafana.sigil.sdk.SigilClient;
+import com.grafana.sigil.sdk.SigilClientConfig;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
+import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
+import io.opentelemetry.sdk.trace.SdkTracerProvider;
+import io.opentelemetry.sdk.trace.data.SpanData;
+import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import org.junit.jupiter.api.Test;
+
+class GoogleAdkConformanceTest {
+ @Test
+ void runLifecycleConformancePropagatesFrameworkMetadataAndParentSpan() throws Exception {
+ try (ConformanceEnv env = new ConformanceEnv()) {
+ Span parent = env.tracerProvider.get("google-adk-framework").spanBuilder("google-adk.parent").startSpan();
+ try (var scope = parent.makeCurrent()) {
+ SigilGoogleAdkAdapter adapter = new SigilGoogleAdkAdapter(env.client, new SigilGoogleAdkAdapter.Options()
+ .setAgentName("planner")
+ .setAgentVersion("1.0.0")
+ .setCaptureInputs(true)
+ .setCaptureOutputs(true));
+
+ adapter.onRunStart(new SigilGoogleAdkAdapter.RunStartEvent()
+ .setRunId("run-sync")
+ .setConversationId("conversation-42")
+ .setThreadId("thread-42")
+ .setParentRunId("parent-run-42")
+ .setEventId("event-42")
+ .setComponentName("planner")
+ .setRunType("chat")
+ .setRetryAttempt(2)
+ .addTag("prod")
+ .addTag("framework")
+ .setModelName("gpt-5")
+ .addPrompt("hello")
+ .putMetadata("team", "infra"));
+ adapter.onRunEnd("run-sync", new SigilGoogleAdkAdapter.RunEndEvent()
+ .setResponseModel("gpt-5")
+ .setStopReason("stop")
+ .setUsage(new com.grafana.sigil.sdk.TokenUsage().setInputTokens(12L).setOutputTokens(4L).setTotalTokens(16L)));
+ } finally {
+ parent.end();
+ }
+
+ Generation generation = env.singleGeneration();
+ SpanData span = env.latestGenerationSpan();
+ List metricNames = env.metricNames();
+
+ assertThat(generation.getTags())
+ .containsEntry("sigil.framework.name", "google-adk")
+ .containsEntry("sigil.framework.source", "handler")
+ .containsEntry("sigil.framework.language", "java");
+ assertThat(generation.getConversationId()).isEqualTo("conversation-42");
+ assertThat(generation.getMetadata())
+ .containsEntry("sigil.framework.run_id", "run-sync")
+ .containsEntry("sigil.framework.run_type", "chat")
+ .containsEntry("sigil.framework.thread_id", "thread-42")
+ .containsEntry("sigil.framework.parent_run_id", "parent-run-42")
+ .containsEntry("sigil.framework.component_name", "planner")
+ .containsEntry("sigil.framework.retry_attempt", 2)
+ .containsEntry("sigil.framework.event_id", "event-42");
+ assertThat(generation.getMetadata().get("sigil.framework.tags")).isEqualTo(List.of("prod", "framework"));
+ assertThat(generation.getMetadata()).containsEntry("team", "infra");
+ assertThat(span.getParentSpanContext().getSpanId()).isEqualTo(parent.getSpanContext().getSpanId());
+ assertThat(metricNames).contains("gen_ai.client.operation.duration");
+ assertThat(metricNames).doesNotContain("gen_ai.client.time_to_first_token");
+ }
+ }
+
+ @Test
+ void streamingConformanceStitchesOutputAndRecordsFirstTokenMetric() throws Exception {
+ try (ConformanceEnv env = new ConformanceEnv()) {
+ SigilGoogleAdkAdapter adapter = new SigilGoogleAdkAdapter(env.client, new SigilGoogleAdkAdapter.Options()
+ .setCaptureInputs(true)
+ .setCaptureOutputs(true));
+
+ adapter.onRunStart(new SigilGoogleAdkAdapter.RunStartEvent()
+ .setRunId("run-stream")
+ .setThreadId("thread-stream-42")
+ .setRunType("chat")
+ .setStream(true)
+ .setModelName("claude-sonnet-4-5")
+ .addPrompt("stream this"));
+ adapter.onRunToken("run-stream", "hello");
+ adapter.onRunToken("run-stream", " world");
+ adapter.onRunEnd("run-stream", new SigilGoogleAdkAdapter.RunEndEvent().setResponseModel("claude-sonnet-4-5"));
+
+ env.client.shutdown();
+
+ Generation generation = env.singleGeneration();
+ SpanData span = env.latestGenerationSpan();
+ List metricNames = env.metricNames();
+
+ assertThat(generation.getMode()).isEqualTo(com.grafana.sigil.sdk.GenerationMode.STREAM);
+ assertThat(generation.getOperationName()).isEqualTo("streamText");
+ assertThat(generation.getOutput()).hasSize(1);
+ assertThat(generation.getOutput().get(0).getRole()).isEqualTo(MessageRole.ASSISTANT);
+ assertThat(generation.getOutput().get(0).getParts()).hasSize(1);
+ assertThat(generation.getOutput().get(0).getParts().get(0).getText()).isEqualTo("hello world");
+ assertThat(span.getAttributes().get(AttributeKey.stringKey("gen_ai.operation.name"))).isEqualTo("streamText");
+ assertThat(metricNames).contains("gen_ai.client.operation.duration", "gen_ai.client.time_to_first_token");
+ }
+ }
+
+ @Test
+ void embeddingsConformanceUsesUnsupportedCapabilityContract() {
+ assertThatThrownBy(SigilGoogleAdkAdapter::checkEmbeddingsSupport)
+ .isInstanceOf(UnsupportedOperationException.class)
+ .hasMessage(
+ "google-adk: embeddings are not supported because the Google ADK lifecycle surface does not expose a dedicated embeddings callback");
+ }
+
+ private static final class ConformanceEnv implements AutoCloseable {
+ private final CapturingExporter exporter = new CapturingExporter();
+ private final InMemorySpanExporter spanExporter = InMemorySpanExporter.create();
+ private final InMemoryMetricReader metricReader = InMemoryMetricReader.create();
+ private final SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
+ .addSpanProcessor(SimpleSpanProcessor.create(spanExporter))
+ .build();
+ private final SdkMeterProvider meterProvider = SdkMeterProvider.builder()
+ .registerMetricReader(metricReader)
+ .build();
+ private final SigilClient client = new SigilClient(new SigilClientConfig()
+ .setTracer(tracerProvider.get("google-adk-conformance"))
+ .setMeter(meterProvider.get("google-adk-conformance"))
+ .setGenerationExporter(exporter)
+ .setGenerationExport(new GenerationExportConfig()
+ .setBatchSize(1)
+ .setQueueSize(10)
+ .setFlushInterval(Duration.ofHours(1))
+ .setMaxRetries(0)));
+
+ Generation singleGeneration() {
+ awaitRequests();
+ assertThat(exporter.requests).hasSize(1);
+ assertThat(exporter.requests.get(0)).hasSize(1);
+ return exporter.requests.get(0).get(0);
+ }
+
+ SpanData latestGenerationSpan() {
+ List spans = spanExporter.getFinishedSpanItems().stream()
+ .filter(span -> {
+ String operation = span.getAttributes().get(AttributeKey.stringKey("gen_ai.operation.name"));
+ return "generateText".equals(operation) || "streamText".equals(operation);
+ })
+ .toList();
+ assertThat(spans).isNotEmpty();
+ return spans.get(spans.size() - 1);
+ }
+
+ List metricNames() {
+ return metricReader.collectAllMetrics().stream()
+ .map(metric -> metric.getName())
+ .distinct()
+ .sorted()
+ .toList();
+ }
+
+ private void awaitRequests() {
+ long deadline = System.nanoTime() + Duration.ofSeconds(5).toNanos();
+ while (System.nanoTime() < deadline) {
+ if (!exporter.requests.isEmpty()) {
+ return;
+ }
+ try {
+ Thread.sleep(10L);
+ } catch (InterruptedException exception) {
+ Thread.currentThread().interrupt();
+ throw new AssertionError("interrupted while waiting for export", exception);
+ }
+ }
+ throw new AssertionError("timed out waiting for generation export");
+ }
+
+ @Override
+ public void close() throws Exception {
+ client.shutdown();
+ meterProvider.close();
+ tracerProvider.close();
+ }
+ }
+
+ private static final class CapturingExporter implements GenerationExporter {
+ private final List> requests = new CopyOnWriteArrayList<>();
+
+ @Override
+ public ExportGenerationsResponse exportGenerations(ExportGenerationsRequest request) {
+ List batch = new ArrayList<>();
+ for (Generation generation : request.getGenerations()) {
+ batch.add(generation.copy());
+ }
+ requests.add(batch);
+
+ List results = new ArrayList<>();
+ for (Generation generation : batch) {
+ results.add(new ExportGenerationResult().setGenerationId(generation.getId()).setAccepted(true));
+ }
+ return new ExportGenerationsResponse().setResults(results);
+ }
+ }
+}
diff --git a/java/frameworks/google-adk/src/test/java/com/grafana/sigil/sdk/frameworks/googleadk/SigilGoogleAdkAdapterTest.java b/java/frameworks/google-adk/src/test/java/com/grafana/sigil/sdk/frameworks/googleadk/SigilGoogleAdkAdapterTest.java
index 3a628ff..05e2107 100644
--- a/java/frameworks/google-adk/src/test/java/com/grafana/sigil/sdk/frameworks/googleadk/SigilGoogleAdkAdapterTest.java
+++ b/java/frameworks/google-adk/src/test/java/com/grafana/sigil/sdk/frameworks/googleadk/SigilGoogleAdkAdapterTest.java
@@ -2,6 +2,11 @@
import static org.assertj.core.api.Assertions.assertThat;
+import com.grafana.sigil.sdk.ExportGenerationResult;
+import com.grafana.sigil.sdk.ExportGenerationsRequest;
+import com.grafana.sigil.sdk.ExportGenerationsResponse;
+import com.grafana.sigil.sdk.Generation;
+import com.grafana.sigil.sdk.GenerationExporter;
import com.grafana.sigil.sdk.GenerationMode;
import com.grafana.sigil.sdk.GenerationRecorder;
import com.grafana.sigil.sdk.GenerationExportConfig;
@@ -13,7 +18,21 @@
import com.grafana.sigil.sdk.ModelRef;
import com.grafana.sigil.sdk.SigilClient;
import com.grafana.sigil.sdk.SigilClientConfig;
+import com.grafana.sigil.sdk.TokenUsage;
import com.grafana.sigil.sdk.ToolExecutionStart;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.sdk.metrics.SdkMeterProvider;
+import io.opentelemetry.sdk.metrics.data.MetricData;
+import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
+import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter;
+import io.opentelemetry.sdk.trace.SdkTracerProvider;
+import io.opentelemetry.sdk.trace.data.SpanData;
+import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
+import java.lang.reflect.Method;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
@@ -157,6 +176,184 @@ void adapterUsesExplicitProviderWhenConfigured() {
}
}
+ @Test
+ void syncRunExportsFrameworkPayloadTagsAndMetrics() {
+ try (FrameworkConformanceEnv env = new FrameworkConformanceEnv()) {
+ SigilGoogleAdkAdapter adapter = new SigilGoogleAdkAdapter(env.client, new SigilGoogleAdkAdapter.Options()
+ .setAgentName("adk-agent")
+ .setAgentVersion("1.0.0")
+ .setCaptureInputs(true)
+ .setCaptureOutputs(true)
+ .putExtraTag("team", "infra")
+ .putExtraMetadata("workspace", "sigil"));
+
+ var parentSpan = env.tracerProvider.get("sigil-framework-test")
+ .spanBuilder("framework.request")
+ .setAttribute(AttributeKey.stringKey("sigil.framework.name"), "google-adk")
+ .setAttribute(AttributeKey.stringKey("sigil.framework.source"), "handler")
+ .setAttribute(AttributeKey.stringKey("sigil.framework.language"), "java")
+ .startSpan();
+ try (Scope ignored = parentSpan.makeCurrent()) {
+ adapter.onRunStart(new SigilGoogleAdkAdapter.RunStartEvent()
+ .setRunId("run-sync")
+ .setSessionId("session-42")
+ .setThreadId("thread-9")
+ .setParentRunId("framework-parent-run")
+ .setComponentName("planner")
+ .setRunType("chat")
+ .setRetryAttempt(2)
+ .setEventId("event-42")
+ .setModelName("gpt-5")
+ .addTag("prod")
+ .addTag("framework")
+ .addPrompt("hello")
+ .putMetadata("phase", "plan"));
+ adapter.onRunEnd("run-sync", new SigilGoogleAdkAdapter.RunEndEvent()
+ .setResponseModel("gpt-5")
+ .setStopReason("stop")
+ .setUsage(new TokenUsage().setInputTokens(3).setOutputTokens(2).setTotalTokens(5))
+ .addOutputMessage(new Message()
+ .setRole(MessageRole.ASSISTANT)
+ .setParts(List.of(MessagePart.text("hi")))));
+ } finally {
+ parentSpan.end();
+ }
+
+ env.client.flush();
+
+ Generation generation = env.exporter.singleGeneration();
+ SpanData generationSpan = env.latestGenerationSpan();
+
+ assertThat(generation.getMode()).isEqualTo(GenerationMode.SYNC);
+ assertThat(generation.getOperationName()).isEqualTo("generateText");
+ assertThat(generation.getConversationId()).isEqualTo("session-42");
+ assertThat(generation.getResponseModel()).isEqualTo("gpt-5");
+ assertThat(generation.getTraceId()).isEqualTo(generationSpan.getTraceId());
+ assertThat(generation.getSpanId()).isEqualTo(generationSpan.getSpanId());
+ assertThat(generation.getTags())
+ .containsEntry("sigil.framework.name", "google-adk")
+ .containsEntry("sigil.framework.source", "handler")
+ .containsEntry("sigil.framework.language", "java")
+ .containsEntry("team", "infra");
+ assertThat(generation.getMetadata())
+ .containsEntry("workspace", "sigil")
+ .containsEntry("phase", "plan")
+ .containsEntry(SigilGoogleAdkAdapter.META_RUN_ID, "run-sync")
+ .containsEntry(SigilGoogleAdkAdapter.META_RUN_TYPE, "chat")
+ .containsEntry(SigilGoogleAdkAdapter.META_THREAD_ID, "thread-9")
+ .containsEntry(SigilGoogleAdkAdapter.META_PARENT_RUN_ID, "framework-parent-run")
+ .containsEntry(SigilGoogleAdkAdapter.META_COMPONENT_NAME, "planner")
+ .containsEntry(SigilGoogleAdkAdapter.META_RETRY_ATTEMPT, 2)
+ .containsEntry(SigilGoogleAdkAdapter.META_EVENT_ID, "event-42")
+ .containsEntry(SigilGoogleAdkAdapter.META_TAGS, List.of("prod", "framework"));
+ assertThat(generation.getOutput()).hasSize(1);
+ assertThat(generation.getOutput().get(0).getParts()).hasSize(1);
+ assertThat(generation.getOutput().get(0).getParts().get(0).getText()).isEqualTo("hi");
+ assertThat(generationSpan.getParentSpanId()).isEqualTo(parentSpan.getSpanContext().getSpanId());
+ assertThat(env.metricNames())
+ .contains("gen_ai.client.operation.duration")
+ .doesNotContain("gen_ai.client.time_to_first_token");
+ }
+ }
+
+ @Test
+ void streamRunExportsStitchedOutputAndTtftMetric() {
+ try (FrameworkConformanceEnv env = new FrameworkConformanceEnv()) {
+ SigilGoogleAdkAdapter adapter = new SigilGoogleAdkAdapter(env.client, new SigilGoogleAdkAdapter.Options()
+ .setCaptureInputs(true)
+ .setCaptureOutputs(true));
+
+ adapter.onRunStart(new SigilGoogleAdkAdapter.RunStartEvent()
+ .setRunId("run-stream-export")
+ .setSessionId("session-stream")
+ .setModelName("claude-sonnet-4-5")
+ .setStream(true)
+ .addPrompt("stream me"));
+ adapter.onRunToken("run-stream-export", "hello");
+ adapter.onRunToken("run-stream-export", " world");
+ adapter.onRunEnd("run-stream-export", new SigilGoogleAdkAdapter.RunEndEvent()
+ .setResponseModel("claude-sonnet-4-5"));
+
+ env.client.flush();
+
+ Generation generation = env.exporter.singleGeneration();
+ assertThat(generation.getMode()).isEqualTo(GenerationMode.STREAM);
+ assertThat(generation.getOperationName()).isEqualTo("streamText");
+ assertThat(generation.getResponseModel()).isEqualTo("claude-sonnet-4-5");
+ assertThat(generation.getOutput()).hasSize(1);
+ assertThat(generation.getOutput().get(0).getParts()).hasSize(1);
+ assertThat(generation.getOutput().get(0).getParts().get(0).getText()).isEqualTo("hello world");
+ assertThat(generation.getTags())
+ .containsEntry("sigil.framework.name", "google-adk")
+ .containsEntry("sigil.framework.source", "handler")
+ .containsEntry("sigil.framework.language", "java");
+ assertThat(env.metricNames())
+ .contains("gen_ai.client.operation.duration", "gen_ai.client.time_to_first_token");
+ }
+ }
+
+ @Test
+ void generationSpanTracksActiveParentSpanAndPreservesExportLineage() {
+ InMemorySpanExporter spanExporter = InMemorySpanExporter.create();
+ SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
+ .addSpanProcessor(SimpleSpanProcessor.create(spanExporter))
+ .build();
+ SigilClient client = new SigilClient(
+ new SigilClientConfig()
+ .setTracer(tracerProvider.get("sigil-framework-test"))
+ .setGenerationExport(
+ new GenerationExportConfig()
+ .setProtocol(GenerationExportProtocol.NONE)));
+ try {
+ SigilGoogleAdkAdapter adapter = new SigilGoogleAdkAdapter(client, new SigilGoogleAdkAdapter.Options()
+ .setCaptureInputs(true)
+ .setCaptureOutputs(true));
+
+ var parentSpan = tracerProvider.get("sigil-framework-test").spanBuilder("framework.request").startSpan();
+ try (Scope ignored = parentSpan.makeCurrent()) {
+ adapter.onRunStart(new SigilGoogleAdkAdapter.RunStartEvent()
+ .setRunId("run-lineage")
+ .setSessionId("session-lineage-42")
+ .setParentRunId("framework-parent-run")
+ .setRunType("chat")
+ .setModelName("gpt-5")
+ .addPrompt("hello"));
+ adapter.onRunEnd("run-lineage", new SigilGoogleAdkAdapter.RunEndEvent()
+ .setResponseModel("gpt-5")
+ .setStopReason("stop"));
+ } finally {
+ parentSpan.end();
+ }
+
+ assertThat(client.debugSnapshot().getGenerations()).hasSize(1);
+ var generation = client.debugSnapshot().getGenerations().get(0);
+ var generationSpan = spanExporter.getFinishedSpanItems().stream()
+ .filter(span -> "generateText".equals(span.getAttributes().get(io.opentelemetry.api.common.AttributeKey.stringKey("gen_ai.operation.name"))))
+ .findFirst()
+ .orElseThrow();
+
+ assertThat(generationSpan.getParentSpanId()).isEqualTo(parentSpan.getSpanContext().getSpanId());
+ assertThat(generationSpan.getTraceId()).isEqualTo(parentSpan.getSpanContext().getTraceId());
+ assertThat(generation.getTraceId()).isEqualTo(generationSpan.getTraceId());
+ assertThat(generation.getSpanId()).isEqualTo(generationSpan.getSpanId());
+ } finally {
+ client.shutdown();
+ tracerProvider.close();
+ }
+ }
+
+ @Test
+ void adapterExplicitlyHasNoEmbeddingLifecycle() {
+ List publicMethodNames = Arrays.stream(SigilGoogleAdkAdapter.class.getMethods())
+ .map(Method::getName)
+ .toList();
+
+ assertThat(publicMethodNames)
+ .doesNotContain("onEmbeddingStart")
+ .doesNotContain("onEmbeddingEnd")
+ .doesNotContain("onEmbeddingError");
+ }
+
@Test
void onRunEndDropsOutputsWhenCaptureOutputsDisabled() {
SigilClient client = newClient();
@@ -377,4 +574,67 @@ void createCallbacksProvidesOneTimeLifecycleWiring() {
client.shutdown();
}
}
+
+ private static final class FrameworkConformanceEnv implements AutoCloseable {
+ private final CapturingExporter exporter = new CapturingExporter();
+ private final InMemorySpanExporter spanExporter = InMemorySpanExporter.create();
+ private final SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
+ .addSpanProcessor(SimpleSpanProcessor.create(spanExporter))
+ .build();
+ private final InMemoryMetricReader metricReader = InMemoryMetricReader.create();
+ private final SdkMeterProvider meterProvider = SdkMeterProvider.builder()
+ .registerMetricReader(metricReader)
+ .build();
+ private final SigilClient client = new SigilClient(new SigilClientConfig()
+ .setTracer(tracerProvider.get("sigil-framework-test"))
+ .setMeter(meterProvider.get("sigil-framework-test"))
+ .setGenerationExporter(exporter)
+ .setGenerationExport(new GenerationExportConfig()
+ .setBatchSize(1)
+ .setQueueSize(10)
+ .setFlushInterval(Duration.ofHours(1))
+ .setMaxRetries(0)));
+
+ private List metricNames() {
+ return metricReader.collectAllMetrics().stream()
+ .map(MetricData::getName)
+ .toList();
+ }
+
+ private SpanData latestGenerationSpan() {
+ return spanExporter.getFinishedSpanItems().stream()
+ .filter(span -> {
+ String operation = span.getAttributes().get(AttributeKey.stringKey("gen_ai.operation.name"));
+ return "generateText".equals(operation) || "streamText".equals(operation);
+ })
+ .reduce((first, second) -> second)
+ .orElseThrow();
+ }
+
+ @Override
+ public void close() {
+ client.shutdown();
+ tracerProvider.close();
+ meterProvider.close();
+ }
+ }
+
+ private static final class CapturingExporter implements GenerationExporter {
+ private final List generations = new ArrayList<>();
+
+ @Override
+ public ExportGenerationsResponse exportGenerations(ExportGenerationsRequest request) {
+ List results = new ArrayList<>();
+ for (Generation generation : request.getGenerations()) {
+ generations.add(generation.copy());
+ results.add(new ExportGenerationResult().setGenerationId(generation.getId()).setAccepted(true));
+ }
+ return new ExportGenerationsResponse().setResults(results);
+ }
+
+ private Generation singleGeneration() {
+ assertThat(generations).hasSize(1);
+ return generations.get(0);
+ }
+ }
}
diff --git a/java/gradle/libs.versions.toml b/java/gradle/libs.versions.toml
index 51826c2..e8349a1 100644
--- a/java/gradle/libs.versions.toml
+++ b/java/gradle/libs.versions.toml
@@ -1,13 +1,13 @@
[versions]
assertj = "3.27.7"
-jackson = "2.21.1"
+jackson = "2.21.2"
jacksonAnnotations = "2.21"
jmh = "0.7.3"
junit = "6.0.3"
-otel = "1.59.0"
-protobuf = "4.33.5"
+otel = "1.60.1"
+protobuf = "4.34.1"
protobufPlugin = "0.9.6"
-grpc = "1.79.0"
+grpc = "1.80.0"
mockwebserver = "5.3.2"
javaxAnnotation = "1.3.2"
@@ -41,9 +41,9 @@ junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" }
mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "mockwebserver" }
javax-annotation = { module = "javax.annotation:javax.annotation-api", version.ref = "javaxAnnotation" }
-openai-java = { module = "com.openai:openai-java", version = "4.22.0" }
-anthropic-java = { module = "com.anthropic:anthropic-java", version = "2.15.0" }
-google-genai = { module = "com.google.genai:google-genai", version = "1.40.0" }
+openai-java = { module = "com.openai:openai-java", version = "4.29.1" }
+anthropic-java = { module = "com.anthropic:anthropic-java", version = "2.18.0" }
+google-genai = { module = "com.google.genai:google-genai", version = "1.44.0" }
[plugins]
protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" }
diff --git a/java/gradle/wrapper/gradle-wrapper.properties b/java/gradle/wrapper/gradle-wrapper.properties
index 37f78a6..c61a118 100644
--- a/java/gradle/wrapper/gradle-wrapper.properties
+++ b/java/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/java/providers/anthropic/src/test/java/com/grafana/sigil/sdk/providers/anthropic/AnthropicAdapterTest.java b/java/providers/anthropic/src/test/java/com/grafana/sigil/sdk/providers/anthropic/AnthropicConformanceTest.java
similarity index 75%
rename from java/providers/anthropic/src/test/java/com/grafana/sigil/sdk/providers/anthropic/AnthropicAdapterTest.java
rename to java/providers/anthropic/src/test/java/com/grafana/sigil/sdk/providers/anthropic/AnthropicConformanceTest.java
index 88f310a..ad00b2c 100644
--- a/java/providers/anthropic/src/test/java/com/grafana/sigil/sdk/providers/anthropic/AnthropicAdapterTest.java
+++ b/java/providers/anthropic/src/test/java/com/grafana/sigil/sdk/providers/anthropic/AnthropicConformanceTest.java
@@ -25,7 +25,7 @@
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
-class AnthropicAdapterTest {
+class AnthropicConformanceTest {
@Test
void syncAndStreamWrappersSetAnthropicProviderAndModes() throws Exception {
CapturingExporter exporter = new CapturingExporter();
@@ -105,6 +105,56 @@ void providerErrorsPopulateCallError() {
assertThat(exporter.generations.get(0).getCallError()).contains("anthropic failed");
}
+ @Test
+ void wrappersTolerateMissingProviderPayloadFields() throws Exception {
+ CapturingExporter exporter = new CapturingExporter();
+ try (SigilClient client = new SigilClient(new SigilClientConfig()
+ .setTracer(GlobalOpenTelemetry.getTracer("test"))
+ .setGenerationExporter(exporter)
+ .setGenerationExport(new GenerationExportConfig().setBatchSize(1).setFlushInterval(Duration.ofMinutes(10)).setMaxRetries(0)))) {
+
+ AnthropicAdapter.completion(
+ client,
+ request(),
+ _r -> ObjectMappers.jsonMapper().readValue(
+ """
+ {
+ "id": "msg_malformed",
+ "content": [],
+ "model": "claude-sonnet-4",
+ "usage": {
+ "input_tokens": 0,
+ "output_tokens": 0
+ }
+ }
+ """,
+ Message.class),
+ new AnthropicOptions());
+ AnthropicAdapter.completionStream(
+ client,
+ request(),
+ _r -> new FakeStreamResponse<>(List.of()),
+ new AnthropicOptions());
+ }
+
+ assertThat(exporter.generations).hasSize(2);
+ assertThat(exporter.generations.get(0).getMode()).isEqualTo(GenerationMode.SYNC);
+ assertThat(exporter.generations.get(0).getResponseId()).isEqualTo("msg_malformed");
+ assertThat(exporter.generations.get(0).getResponseModel()).isEqualTo("claude-sonnet-4");
+ assertThat(exporter.generations.get(0).getOutput()).isEmpty();
+ assertThat(exporter.generations.get(0).getStopReason()).isEmpty();
+ assertThat(exporter.generations.get(1).getMode()).isEqualTo(GenerationMode.STREAM);
+ assertThat(exporter.generations.get(1).getResponseModel()).isEqualTo("claude-sonnet-4");
+ assertThat(exporter.generations.get(1).getOutput()).isEmpty();
+ }
+
+ @Test
+ void embeddingConformanceIsExplicitlyUnsupportedWithoutPublicSurface() {
+ assertThat(AnthropicAdapter.class).isNotNull();
+ assertThatThrownBy(() -> Class.forName("com.grafana.sigil.sdk.providers.anthropic.AnthropicEmbeddings"))
+ .isInstanceOf(ClassNotFoundException.class);
+ }
+
@Test
void mapperSetsThinkingFalseWhenDisabled() throws Exception {
MessageCreateParams request = MessageCreateParams.builder()
@@ -118,6 +168,12 @@ void mapperSetsThinkingFalseWhenDisabled() throws Exception {
assertThat(mapped.getThinkingEnabled()).isFalse();
}
+ @Test
+ void mapperRejectsMissingResponse() {
+ assertThatThrownBy(() -> AnthropicAdapter.fromRequestResponse(request(), null, new AnthropicOptions()))
+ .isInstanceOf(NullPointerException.class);
+ }
+
private static MessageCreateParams request() {
return MessageCreateParams.builder()
.model("claude-sonnet-4")
diff --git a/java/providers/gemini/README.md b/java/providers/gemini/README.md
index 7be3b9e..437fd5a 100644
--- a/java/providers/gemini/README.md
+++ b/java/providers/gemini/README.md
@@ -9,9 +9,11 @@ No simplified public DTO layer is exposed.
- Wrappers:
- `GeminiAdapter.completion(...)`
- `GeminiAdapter.completionStream(...)`
+ - `GeminiAdapter.embedContent(...)`
- Manual mappers:
- `GeminiAdapter.fromRequestResponse(...)`
- `GeminiAdapter.fromStream(...)`
+ - `GeminiAdapter.embeddingFromResponse(...)`
## Official SDK Types
@@ -54,6 +56,22 @@ GeminiStreamSummary summary = GeminiAdapter.completionStream(
);
```
+## Embedding Example
+
+```java
+EmbedContentResponse embeddingResponse = GeminiAdapter.embedContent(
+ sigilClient,
+ "gemini-embedding-001",
+ java.util.List.of("hello", "world"),
+ null,
+ (model, input, cfg) -> genai.models.embedContent(model, input, cfg),
+ new GeminiOptions()
+ .setConversationId("conv-1")
+ .setAgentName("assistant-gemini")
+ .setAgentVersion("1.0.0")
+);
+```
+
## Raw Artifact Policy
- Default: OFF
diff --git a/java/providers/gemini/src/test/java/com/grafana/sigil/sdk/providers/gemini/GeminiAdapterTest.java b/java/providers/gemini/src/test/java/com/grafana/sigil/sdk/providers/gemini/GeminiConformanceTest.java
similarity index 84%
rename from java/providers/gemini/src/test/java/com/grafana/sigil/sdk/providers/gemini/GeminiAdapterTest.java
rename to java/providers/gemini/src/test/java/com/grafana/sigil/sdk/providers/gemini/GeminiConformanceTest.java
index a40303d..79ee046 100644
--- a/java/providers/gemini/src/test/java/com/grafana/sigil/sdk/providers/gemini/GeminiAdapterTest.java
+++ b/java/providers/gemini/src/test/java/com/grafana/sigil/sdk/providers/gemini/GeminiConformanceTest.java
@@ -26,7 +26,7 @@
import java.util.concurrent.CopyOnWriteArrayList;
import org.junit.jupiter.api.Test;
-class GeminiAdapterTest {
+class GeminiConformanceTest {
@Test
void syncAndStreamWrappersSetGeminiProviderAndModes() throws Exception {
CapturingExporter exporter = new CapturingExporter();
@@ -105,6 +105,53 @@ void providerErrorsPopulateCallError() {
assertThat(exporter.generations.get(0).getCallError()).contains("gemini failed");
}
+ @Test
+ void wrappersTolerateMissingProviderPayloadFields() throws Exception {
+ CapturingExporter exporter = new CapturingExporter();
+ try (SigilClient client = new SigilClient(new SigilClientConfig()
+ .setTracer(GlobalOpenTelemetry.getTracer("test"))
+ .setGenerationExporter(exporter)
+ .setGenerationExport(new GenerationExportConfig().setBatchSize(1).setFlushInterval(Duration.ofMinutes(10)).setMaxRetries(0)))) {
+
+ GeminiAdapter.completion(
+ client,
+ model(),
+ contents(),
+ config(),
+ (_m, _c, _cfg) -> GenerateContentResponse.fromJson(
+ """
+ {
+ "responseId": "resp_malformed",
+ "modelVersion": "gemini-2.5-pro-001",
+ "candidates": []
+ }
+ """),
+ new GeminiOptions());
+ GeminiAdapter.completionStream(
+ client,
+ model(),
+ contents(),
+ config(),
+ (_m, _c, _cfg) -> List.of(GenerateContentResponse.fromJson(
+ """
+ {
+ "modelVersion": "gemini-2.5-pro-001"
+ }
+ """)),
+ new GeminiOptions());
+ }
+
+ assertThat(exporter.generations).hasSize(2);
+ assertThat(exporter.generations.get(0).getMode()).isEqualTo(GenerationMode.SYNC);
+ assertThat(exporter.generations.get(0).getResponseId()).isEqualTo("resp_malformed");
+ assertThat(exporter.generations.get(0).getResponseModel()).isEqualTo("gemini-2.5-pro-001");
+ assertThat(exporter.generations.get(0).getOutput()).isEmpty();
+ assertThat(exporter.generations.get(0).getStopReason()).isEmpty();
+ assertThat(exporter.generations.get(1).getMode()).isEqualTo(GenerationMode.STREAM);
+ assertThat(exporter.generations.get(1).getResponseModel()).isEqualTo("gemini-2.5-pro-001");
+ assertThat(exporter.generations.get(1).getOutput()).isEmpty();
+ }
+
@Test
void embeddingWrapperDoesNotEnqueueGenerations() throws Exception {
CapturingExporter exporter = new CapturingExporter();
diff --git a/java/providers/openai/README.md b/java/providers/openai/README.md
index a3bca17..a14c72d 100644
--- a/java/providers/openai/README.md
+++ b/java/providers/openai/README.md
@@ -19,6 +19,9 @@ No simplified OpenAI DTO layer is exposed.
- `OpenAiResponses.createStreaming(...)`
- `OpenAiResponses.fromRequestResponse(...)`
- `OpenAiResponses.fromStream(...)`
+- Embeddings:
+ - `OpenAiEmbeddings.create(...)`
+ - `OpenAiEmbeddings.fromRequestResponse(...)`
## Integration styles
@@ -72,6 +75,23 @@ Response response = OpenAiResponses.create(
);
```
+## Embeddings Example
+
+```java
+CreateEmbeddingResponse embeddingResponse = OpenAiEmbeddings.create(
+ sigilClient,
+ EmbeddingCreateParams.builder()
+ .model("text-embedding-3-small")
+ .inputOfArrayOfStrings(java.util.List.of("hello", "world"))
+ .build(),
+ params -> openAI.embeddings().create(params),
+ new OpenAiOptions()
+ .setConversationId("conv-1")
+ .setAgentName("assistant-openai")
+ .setAgentVersion("1.0.0")
+);
+```
+
## Manual instrumentation example (strict mapper)
```java
diff --git a/java/providers/openai/src/test/java/com/grafana/sigil/sdk/providers/openai/OpenAiMappingAndRecorderTests.java b/java/providers/openai/src/test/java/com/grafana/sigil/sdk/providers/openai/OpenAiConformanceTest.java
similarity index 88%
rename from java/providers/openai/src/test/java/com/grafana/sigil/sdk/providers/openai/OpenAiMappingAndRecorderTests.java
rename to java/providers/openai/src/test/java/com/grafana/sigil/sdk/providers/openai/OpenAiConformanceTest.java
index d577cff..cc34495 100644
--- a/java/providers/openai/src/test/java/com/grafana/sigil/sdk/providers/openai/OpenAiMappingAndRecorderTests.java
+++ b/java/providers/openai/src/test/java/com/grafana/sigil/sdk/providers/openai/OpenAiConformanceTest.java
@@ -34,7 +34,7 @@
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
-class OpenAiMappingAndRecorderTests {
+class OpenAiConformanceTest {
@Test
void chatSyncWrapperSetsSyncModeAndRawArtifactsOffByDefault() throws Exception {
CapturingExporter exporter = new CapturingExporter();
@@ -137,6 +137,58 @@ void providerErrorsPopulateCallErrorForChatAndResponses() throws Exception {
}
}
+ @Test
+ void wrappersTolerateMissingProviderPayloadFields() throws Exception {
+ try (SigilClient client = newClient(new CapturingExporter())) {
+ OpenAiChatCompletions.create(
+ client,
+ chatRequestFixture(),
+ _request -> json(
+ """
+ {
+ "id": "chatcmpl_malformed",
+ "choices": [],
+ "created": 1,
+ "model": "gpt-5",
+ "object": "chat.completion"
+ }
+ """,
+ ChatCompletion.class),
+ new OpenAiOptions());
+ Generation chatGeneration = singleDebugGeneration(client);
+ assertThat(chatGeneration.getMode()).isEqualTo(GenerationMode.SYNC);
+ assertThat(chatGeneration.getResponseId()).isEqualTo("chatcmpl_malformed");
+ assertThat(chatGeneration.getResponseModel()).isEqualTo("gpt-5");
+ assertThat(chatGeneration.getOutput()).isEmpty();
+ assertThat(chatGeneration.getStopReason()).isEmpty();
+ }
+
+ try (SigilClient client = newClient(new CapturingExporter())) {
+ OpenAiResponses.createStreaming(
+ client,
+ responsesRequestFixture(),
+ _request -> new FakeStreamResponse<>(List.of(
+ json(
+ """
+ {
+ "type": "response.output_text.delta",
+ "content_index": 0,
+ "delta": 42,
+ "item_id": "msg_1",
+ "output_index": 0,
+ "sequence_number": 1
+ }
+ """,
+ ResponseStreamEvent.class))),
+ new OpenAiOptions());
+ Generation streamGeneration = singleDebugGeneration(client);
+ assertThat(streamGeneration.getMode()).isEqualTo(GenerationMode.STREAM);
+ assertThat(streamGeneration.getOutput()).hasSize(1);
+ assertThat(streamGeneration.getOutput().get(0).getParts()).hasSize(1);
+ assertThat(streamGeneration.getOutput().get(0).getParts().get(0).getText()).isEqualTo("42");
+ }
+ }
+
@Test
void embeddingsWrapperDoesNotEnqueueGenerations() throws Exception {
CapturingExporter exporter = new CapturingExporter();
diff --git a/java/settings.gradle.kts b/java/settings.gradle.kts
index aa61a8d..e352393 100644
--- a/java/settings.gradle.kts
+++ b/java/settings.gradle.kts
@@ -6,7 +6,6 @@ include(":providers:anthropic")
include(":providers:gemini")
include(":frameworks:google-adk")
include(":benchmarks")
-include(":devex-emitter")
project(":providers:openai").projectDir = file("providers/openai")
project(":providers:anthropic").projectDir = file("providers/anthropic")
diff --git a/js/LICENSE b/js/LICENSE
index ae8c60c..626a3ab 100644
--- a/js/LICENSE
+++ b/js/LICENSE
@@ -1,3 +1,3 @@
SPDX-License-Identifier: Apache-2.0
-See /sdks/LICENSE at repository root for full license text.
+See /LICENSE at repository root for full license text.
diff --git a/js/README.md b/js/README.md
index 2865c75..2c35f2d 100644
--- a/js/README.md
+++ b/js/README.md
@@ -8,6 +8,20 @@ Sigil records normalized LLM generation and tool-execution telemetry using your
pnpm add @grafana/sigil-sdk-js
```
+## Validation
+
+Run the shared core conformance suite for the JavaScript SDK from the repo root:
+
+```bash
+mise run test:ts:sdk-conformance
+```
+
+Run the cross-language aggregate core conformance suite from the repo root:
+
+```bash
+mise run sdk:conformance
+```
+
## Quick Start
```ts
@@ -257,6 +271,7 @@ Auth is configured for `generationExport`.
- `mode: "none"`
- `mode: "tenant"` (requires `tenantId`, injects `X-Scope-OrgID`)
- `mode: "bearer"` (requires `bearerToken`, injects `Authorization: Bearer `)
+- `mode: "basic"` (requires `basicPassword` + `basicUser` or `tenantId`, injects `Authorization: Basic `; also injects `X-Scope-OrgID` when `tenantId` is set — for self-hosted multi-tenancy only, not needed for Grafana Cloud)
Invalid mode/field combinations throw during client config resolution.
@@ -275,6 +290,35 @@ const client = new SigilClient({
});
```
+### Grafana Cloud auth (basic)
+
+For Grafana Cloud, use `basic` auth mode. The username is your Grafana Cloud instance/tenant ID and the password is your Grafana Cloud API key:
+
+```ts
+const client = new SigilClient({
+ generationExport: {
+ protocol: "http",
+ endpoint: "https://.grafana.net/api/v1/generations:export",
+ auth: {
+ mode: "basic",
+ tenantId: process.env.GRAFANA_CLOUD_INSTANCE_ID,
+ basicPassword: process.env.GRAFANA_CLOUD_API_KEY,
+ },
+ },
+});
+```
+
+If your deployment requires a distinct username, set `basicUser` explicitly:
+
+```ts
+auth: {
+ mode: "basic",
+ tenantId: process.env.GRAFANA_CLOUD_INSTANCE_ID,
+ basicUser: process.env.GRAFANA_CLOUD_INSTANCE_ID,
+ basicPassword: process.env.GRAFANA_CLOUD_API_KEY,
+},
+```
+
## Env-secret wiring example
The SDK does not auto-load env vars. Resolve env secrets in your app and map them into config.
@@ -299,7 +343,8 @@ const client = new SigilClient({
Common topology:
-- Generations direct to Sigil: generation `tenant` mode.
+- Grafana Cloud: generation `basic` mode with instance ID and API key.
+- Self-hosted direct to Sigil: generation `tenant` mode.
- Traces/metrics via OTEL Collector/Alloy: configure exporters in your app OTEL SDK setup.
- Enterprise proxy: generation `bearer` mode to proxy; proxy authenticates and forwards tenant header upstream.
diff --git a/js/docs/providers/gemini.md b/js/docs/providers/gemini.md
index f2b4027..25982e6 100644
--- a/js/docs/providers/gemini.md
+++ b/js/docs/providers/gemini.md
@@ -7,9 +7,11 @@ This helper maps strict Gemini `model/contents/config` payloads into Sigil `Gene
- Wrapper calls:
- `gemini.models.generateContent(client, model, contents, config, providerCall, options?)`
- `gemini.models.generateContentStream(client, model, contents, config, providerCall, options?)`
+ - `gemini.models.embedContent(client, model, contents, config, providerCall, options?)`
- Mapper functions:
- `gemini.models.fromRequestResponse(model, contents, config, response, options?)`
- `gemini.models.fromStream(model, contents, config, summary, options?)`
+ - `gemini.models.embeddingFromResponse(model, contents, config, response)`
- Raw artifacts (debug opt-in):
- `request`
- `response` (sync)
@@ -54,6 +56,21 @@ try {
}
```
+## Embedding example
+
+```ts
+const embeddingResponse = await gemini.models.embedContent(
+ client,
+ 'gemini-embedding-001',
+ [{ parts: [{ text: 'hello' }] }, { parts: [{ text: 'world' }] }],
+ { outputDimensionality: 256 },
+ async (reqModel, reqContents, reqConfig) =>
+ provider.models.embedContent({ model: reqModel, contents: reqContents, config: reqConfig })
+);
+
+console.log(embeddingResponse.embeddings?.length ?? 0);
+```
+
## Raw artifact policy
- Default OFF.
diff --git a/js/docs/providers/openai.md b/js/docs/providers/openai.md
index 9022117..045aa84 100644
--- a/js/docs/providers/openai.md
+++ b/js/docs/providers/openai.md
@@ -18,6 +18,11 @@ This helper now mirrors official OpenAI SDK shapes for both Chat Completions and
- `openai.responses.fromRequestResponse(request, response, options?)`
- `openai.responses.fromStream(request, summary, options?)`
+- Embeddings wrapper:
+ - `openai.embeddings.create(client, request, providerCall, options?)`
+- Embeddings mapper:
+ - `openai.embeddings.fromRequestResponse(request, response)`
+
## Integration styles
- Strict wrappers: call OpenAI and record in one step.
@@ -100,6 +105,21 @@ try {
}
```
+## Embeddings example
+
+```ts
+const embeddingResponse = await openai.embeddings.create(
+ sigil,
+ {
+ model: 'text-embedding-3-small',
+ input: ['hello', 'world'],
+ },
+ async (request) => provider.embeddings.create(request)
+);
+
+console.log(embeddingResponse.model);
+```
+
## Raw artifact policy
Raw artifacts are OFF by default.
diff --git a/js/package.json b/js/package.json
index 2c3ec0f..718b263 100644
--- a/js/package.json
+++ b/js/package.json
@@ -53,25 +53,27 @@
"test:ci": "pnpm run test"
},
"dependencies": {
- "@anthropic-ai/sdk": "^0.78.0",
+ "@anthropic-ai/sdk": "^0.80.0",
+ "@google/adk": "^0.5.0",
"@google/genai": "^1.41.0",
- "@google/adk": "^0.3.0",
"@grpc/grpc-js": "^1.14.1",
"@grpc/proto-loader": "^0.8.0",
"@langchain/core": "^1.0.0",
"@langchain/langgraph": "^1.2.0",
- "@openai/agents": "^0.5.0",
+ "@openai/agents": "^0.8.0",
"@opentelemetry/api": "^1.9.0",
- "@opentelemetry/exporter-metrics-otlp-grpc": "^0.212.0",
- "@opentelemetry/exporter-metrics-otlp-http": "^0.212.0",
- "@opentelemetry/exporter-trace-otlp-grpc": "^0.212.0",
- "@opentelemetry/exporter-trace-otlp-http": "^0.212.0",
+ "@opentelemetry/exporter-metrics-otlp-grpc": "^0.213.0",
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.213.0",
+ "@opentelemetry/exporter-trace-otlp-grpc": "^0.213.0",
+ "@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
"@opentelemetry/sdk-metrics": "^2.1.0",
"@opentelemetry/sdk-trace-base": "^2.5.0",
- "openai": "^6.21.0",
- "llamaindex": "^0.12.1"
+ "llamaindex": "^0.12.1",
+ "openai": "^6.27.0"
},
"devDependencies": {
- "typescript": "^5.9.3"
+ "@opentelemetry/context-async-hooks": "^2.6.0",
+ "@types/node": "^24.11.0",
+ "typescript": "^6.0.0"
}
}
diff --git a/js/proto/sigil/v1/generation_ingest.proto b/js/proto/sigil/v1/generation_ingest.proto
index 733a27f..fd7355d 100644
--- a/js/proto/sigil/v1/generation_ingest.proto
+++ b/js/proto/sigil/v1/generation_ingest.proto
@@ -83,6 +83,7 @@ message ToolDefinition {
string description = 2;
string type = 3;
bytes input_schema_json = 4;
+ bool deferred = 5;
}
message TokenUsage {
@@ -92,6 +93,7 @@ message TokenUsage {
int64 cache_read_input_tokens = 4;
int64 cache_write_input_tokens = 5;
int64 reasoning_tokens = 6;
+ int64 cache_creation_input_tokens = 7;
}
enum ArtifactKind {
diff --git a/js/src/client.ts b/js/src/client.ts
index 0f5404b..a1a944b 100644
--- a/js/src/client.ts
+++ b/js/src/client.ts
@@ -1,4 +1,11 @@
import { defaultLogger, mergeConfig } from './config.js';
+import {
+ agentNameFromContext,
+ agentVersionFromContext,
+ conversationIdFromContext,
+ conversationTitleFromContext,
+ userIdFromContext,
+} from './context.js';
import { createDefaultGenerationExporter } from './exporters/default.js';
import { metrics, SpanKind, SpanStatusCode, trace, type Histogram, type Meter, type Span, type Tracer } from '@opentelemetry/api';
import type {
@@ -34,11 +41,13 @@ import {
cloneEmbeddingStart,
cloneGeneration,
cloneGenerationResult,
+ cloneGenerationStart,
cloneModelRef,
cloneToolDefinition,
cloneMessage,
cloneArtifact,
cloneToolExecution,
+ cloneToolExecutionStart,
cloneToolExecutionResult,
defaultOperationNameForMode,
defaultSleep,
@@ -62,6 +71,8 @@ const spanAttrFrameworkRetryAttempt = 'sigil.framework.retry_attempt';
const spanAttrFrameworkLangGraphNode = 'sigil.framework.langgraph.node';
const spanAttrFrameworkEventID = 'sigil.framework.event_id';
const spanAttrConversationID = 'gen_ai.conversation.id';
+const spanAttrConversationTitle = 'sigil.conversation.title';
+const spanAttrUserID = 'user.id';
const spanAttrAgentName = 'gen_ai.agent.name';
const spanAttrAgentVersion = 'gen_ai.agent.version';
const spanAttrErrorType = 'error.type';
@@ -116,6 +127,8 @@ const metricTokenTypeReasoning = 'reasoning';
const instrumentationName = 'github.com/grafana/sigil/sdks/js';
const sdkName = 'sdk-js';
const defaultEmbeddingOperationName = 'embeddings';
+const metadataUserIDKey = 'sigil.user.id';
+const metadataLegacyUserIDKey = 'user.id';
export class SigilClient {
private readonly config: SigilSdkConfig;
@@ -221,7 +234,14 @@ export class SigilClient {
callback?: RecorderCallback
): EmbeddingRecorder | Promise {
this.assertOpen();
- const recorder = new EmbeddingRecorderImpl(this, start);
+ const seed = cloneEmbeddingStart(start);
+ if (!notEmpty(seed.agentName)) {
+ seed.agentName = agentNameFromContext();
+ }
+ if (!notEmpty(seed.agentVersion)) {
+ seed.agentVersion = agentVersionFromContext();
+ }
+ const recorder = new EmbeddingRecorderImpl(this, seed);
if (callback === undefined) {
return recorder;
}
@@ -413,6 +433,8 @@ export class SigilClient {
setGenerationSpanAttributes(span, {
id: seed.id,
conversationId: seed.conversationId,
+ conversationTitle: seed.conversationTitle,
+ userId: seed.userId,
agentName: seed.agentName,
agentVersion: seed.agentVersion,
operationName,
@@ -457,6 +479,10 @@ export class SigilClient {
}
}
+ internalSyncGenerationSpan(span: Span, generation: Generation): void {
+ setGenerationSpanAttributes(span, generation);
+ }
+
internalFinalizeGenerationSpan(
span: Span,
generation: Generation,
@@ -466,7 +492,6 @@ export class SigilClient {
firstTokenAt: Date | undefined
): void {
span.updateName(generationSpanName(generation.operationName, generation.model.name));
- setGenerationSpanAttributes(span, generation);
if (callError !== undefined) {
span.recordException(new Error(callError));
@@ -686,8 +711,9 @@ export class SigilClient {
const errorCategory = finalError === undefined ? '' : errorCategoryFromError(finalError, true);
this.operationDurationHistogram.record(durationSeconds, {
[spanAttrOperationName]: 'execute_tool',
- [spanAttrProviderName]: '',
- [spanAttrRequestModel]: toolExecution.toolName,
+ [spanAttrProviderName]: (toolExecution.requestProvider ?? '').trim(),
+ [spanAttrRequestModel]: (toolExecution.requestModel ?? '').trim(),
+ [spanAttrToolName]: toolExecution.toolName.trim(),
[spanAttrAgentName]: toolExecution.agentName ?? '',
[spanAttrErrorType]: errorType,
[spanAttrErrorCategory]: errorCategory,
@@ -801,6 +827,7 @@ export class SigilClient {
}
class GenerationRecorderImpl implements GenerationRecorder {
+ private readonly seed: GenerationStart;
private readonly startedAt: Date;
private readonly mode: GenerationMode;
private readonly span: Span;
@@ -812,12 +839,31 @@ class GenerationRecorderImpl implements GenerationRecorder {
constructor(
private readonly client: SigilClient,
- private readonly seed: GenerationStart,
+ seed: GenerationStart,
defaultMode: GenerationMode
) {
- this.mode = seed.mode ?? defaultMode;
- this.startedAt = seed.startedAt ?? this.client.internalNow();
- this.span = this.client.internalStartGenerationSpan(seed, this.mode, this.startedAt);
+ this.seed = cloneGenerationStart(seed);
+ if (!notEmpty(this.seed.conversationId)) {
+ this.seed.conversationId = conversationIdFromContext();
+ }
+ if (!notEmpty(this.seed.conversationTitle)) {
+ this.seed.conversationTitle = conversationTitleFromContext();
+ }
+ if (!notEmpty(this.seed.userId)) {
+ this.seed.userId = userIdFromContext();
+ }
+ if (!notEmpty(this.seed.agentName)) {
+ this.seed.agentName = agentNameFromContext();
+ }
+ if (!notEmpty(this.seed.agentVersion)) {
+ this.seed.agentVersion = agentVersionFromContext();
+ }
+ if (!notEmpty(this.seed.operationName)) {
+ this.seed.operationName = defaultOperationNameForMode(this.seed.mode ?? defaultMode);
+ }
+ this.mode = this.seed.mode ?? defaultMode;
+ this.startedAt = this.seed.startedAt ?? this.client.internalNow();
+ this.span = this.client.internalStartGenerationSpan(this.seed, this.mode, this.startedAt);
}
setResult(result: GenerationResult): void {
@@ -852,9 +898,11 @@ class GenerationRecorderImpl implements GenerationRecorder {
const generation: Generation = {
id: this.seed.id ?? newLocalID('gen'),
- conversationId: this.result?.conversationId ?? this.seed.conversationId,
- agentName: this.result?.agentName ?? this.seed.agentName,
- agentVersion: this.result?.agentVersion ?? this.seed.agentVersion,
+ conversationId: firstNonEmptyString(this.result?.conversationId, this.seed.conversationId),
+ conversationTitle: firstNonEmptyString(this.result?.conversationTitle, this.seed.conversationTitle),
+ userId: firstNonEmptyString(this.result?.userId, this.seed.userId),
+ agentName: firstNonEmptyString(this.result?.agentName, this.seed.agentName),
+ agentVersion: firstNonEmptyString(this.result?.agentVersion, this.seed.agentVersion),
mode: this.mode,
operationName: this.result?.operationName ?? this.seed.operationName ?? defaultOperationNameForMode(this.mode),
model: cloneModelRef(this.seed.model),
@@ -873,16 +921,35 @@ class GenerationRecorderImpl implements GenerationRecorder {
stopReason: this.result?.stopReason,
startedAt: new Date(this.startedAt),
completedAt: new Date(this.result?.completedAt ?? this.client.internalNow()),
- tags: this.result?.tags ? { ...this.result.tags } : this.seed.tags ? { ...this.seed.tags } : undefined,
- metadata: this.result?.metadata
- ? { ...this.result.metadata }
- : this.seed.metadata
- ? { ...this.seed.metadata }
- : undefined,
+ tags: mergeStringRecords(this.seed.tags, this.result?.tags),
+ metadata: mergeUnknownRecords(this.seed.metadata, this.result?.metadata),
artifacts: this.result?.artifacts?.map(cloneArtifact),
callError: this.callError,
};
+ generation.conversationTitle = firstNonEmptyString(
+ generation.conversationTitle,
+ metadataStringValue(generation.metadata, spanAttrConversationTitle)
+ )?.trim();
+ if (notEmpty(generation.conversationTitle)) {
+ if (generation.metadata === undefined) {
+ generation.metadata = {};
+ }
+ generation.metadata[spanAttrConversationTitle] = generation.conversationTitle;
+ }
+
+ generation.userId = firstNonEmptyString(
+ generation.userId,
+ metadataStringValue(generation.metadata, metadataUserIDKey),
+ metadataStringValue(generation.metadata, metadataLegacyUserIDKey)
+ )?.trim();
+ if (notEmpty(generation.userId)) {
+ if (generation.metadata === undefined) {
+ generation.metadata = {};
+ }
+ generation.metadata[metadataUserIDKey] = generation.userId;
+ }
+
if (this.callError !== undefined) {
if (generation.metadata === undefined) {
generation.metadata = {};
@@ -894,6 +961,7 @@ class GenerationRecorderImpl implements GenerationRecorder {
}
generation.metadata[spanAttrSDKName] = sdkName;
+ this.client.internalSyncGenerationSpan(this.span, generation);
this.client.internalApplyTraceContextFromSpan(this.span, generation);
this.client.internalRecordGeneration(generation);
@@ -993,6 +1061,7 @@ class EmbeddingRecorderImpl implements EmbeddingRecorder {
}
class ToolExecutionRecorderImpl implements ToolExecutionRecorder {
+ private readonly seed: ToolExecutionStart;
private readonly startedAt: Date;
private readonly span: Span;
private ended = false;
@@ -1002,10 +1071,23 @@ class ToolExecutionRecorderImpl implements ToolExecutionRecorder {
constructor(
private readonly client: SigilClient,
- private readonly seed: ToolExecutionStart
+ seed: ToolExecutionStart
) {
- this.startedAt = seed.startedAt ?? this.client.internalNow();
- this.span = this.client.internalStartToolExecutionSpan(seed, this.startedAt);
+ this.seed = cloneToolExecutionStart(seed);
+ if (!notEmpty(this.seed.conversationId)) {
+ this.seed.conversationId = conversationIdFromContext();
+ }
+ if (!notEmpty(this.seed.conversationTitle)) {
+ this.seed.conversationTitle = conversationTitleFromContext();
+ }
+ if (!notEmpty(this.seed.agentName)) {
+ this.seed.agentName = agentNameFromContext();
+ }
+ if (!notEmpty(this.seed.agentVersion)) {
+ this.seed.agentVersion = agentVersionFromContext();
+ }
+ this.startedAt = this.seed.startedAt ?? this.client.internalNow();
+ this.span = this.client.internalStartToolExecutionSpan(this.seed, this.startedAt);
}
setResult(result: ToolExecutionResult): void {
@@ -1035,8 +1117,11 @@ class ToolExecutionRecorderImpl implements ToolExecutionRecorder {
toolType: this.seed.toolType,
toolDescription: this.seed.toolDescription,
conversationId: this.seed.conversationId,
+ conversationTitle: this.seed.conversationTitle,
agentName: this.seed.agentName,
agentVersion: this.seed.agentVersion,
+ requestModel: this.seed.requestModel,
+ requestProvider: this.seed.requestProvider,
includeContent: this.seed.includeContent ?? false,
startedAt: new Date(this.startedAt),
completedAt: new Date(this.result?.completedAt ?? this.client.internalNow()),
@@ -1122,6 +1207,8 @@ function setGenerationSpanAttributes(
generation: {
id?: string;
conversationId?: string;
+ conversationTitle?: string;
+ userId?: string;
agentName?: string;
agentVersion?: string;
operationName: string;
@@ -1154,6 +1241,12 @@ function setGenerationSpanAttributes(
if (notEmpty(generation.conversationId)) {
span.setAttribute(spanAttrConversationID, generation.conversationId);
}
+ if (notEmpty(generation.conversationTitle)) {
+ span.setAttribute(spanAttrConversationTitle, generation.conversationTitle);
+ }
+ if (notEmpty(generation.userId)) {
+ span.setAttribute(spanAttrUserID, generation.userId);
+ }
if (notEmpty(generation.agentName)) {
span.setAttribute(spanAttrAgentName, generation.agentName);
}
@@ -1309,8 +1402,11 @@ function setToolSpanAttributes(
toolType?: string;
toolDescription?: string;
conversationId?: string;
+ conversationTitle?: string;
agentName?: string;
agentVersion?: string;
+ requestProvider?: string;
+ requestModel?: string;
}
): void {
span.setAttribute(spanAttrOperationName, 'execute_tool');
@@ -1329,12 +1425,21 @@ function setToolSpanAttributes(
if (notEmpty(tool.conversationId)) {
span.setAttribute(spanAttrConversationID, tool.conversationId);
}
+ if (notEmpty(tool.conversationTitle)) {
+ span.setAttribute(spanAttrConversationTitle, tool.conversationTitle);
+ }
if (notEmpty(tool.agentName)) {
span.setAttribute(spanAttrAgentName, tool.agentName);
}
if (notEmpty(tool.agentVersion)) {
span.setAttribute(spanAttrAgentVersion, tool.agentVersion);
}
+ if (notEmpty(tool.requestProvider)) {
+ span.setAttribute(spanAttrProviderName, tool.requestProvider);
+ }
+ if (notEmpty(tool.requestModel)) {
+ span.setAttribute(spanAttrRequestModel, tool.requestModel);
+ }
}
function serializeToolContent(value: unknown): { value?: string; error?: Error } {
@@ -1618,6 +1723,41 @@ function metadataIntValue(metadata: Record | undefined, key: st
return undefined;
}
+function firstNonEmptyString(...values: Array): string | undefined {
+ for (const value of values) {
+ if (notEmpty(value)) {
+ return value;
+ }
+ }
+ return undefined;
+}
+
+function mergeStringRecords(
+ left: Record | undefined,
+ right: Record | undefined
+): Record | undefined {
+ if (left === undefined && right === undefined) {
+ return undefined;
+ }
+ return {
+ ...(left ?? {}),
+ ...(right ?? {}),
+ };
+}
+
+function mergeUnknownRecords(
+ left: Record | undefined,
+ right: Record | undefined
+): Record | undefined {
+ if (left === undefined && right === undefined) {
+ return undefined;
+ }
+ return {
+ ...(left ?? {}),
+ ...(right ?? {}),
+ };
+}
+
function countToolCallParts(messages: Message[]): number {
let total = 0;
for (const message of messages) {
diff --git a/js/src/config.ts b/js/src/config.ts
index 2ffa342..4484314 100644
--- a/js/src/config.ts
+++ b/js/src/config.ts
@@ -120,8 +120,10 @@ function resolveHeadersWithAuth(
const out = headers ? { ...headers } : undefined;
if (mode === 'none') {
- if (tenantId.length > 0 || bearerToken.length > 0) {
- throw new Error(`${label} auth mode "none" does not allow tenantId or bearerToken`);
+ const basicUser = auth.basicUser?.trim() ?? '';
+ const basicPassword = auth.basicPassword?.trim() ?? '';
+ if (tenantId.length > 0 || bearerToken.length > 0 || basicUser.length > 0 || basicPassword.length > 0) {
+ throw new Error(`${label} auth mode "none" does not allow credentials`);
}
return out;
}
@@ -158,6 +160,29 @@ function resolveHeadersWithAuth(
};
}
+ if (mode === 'basic') {
+ const password = auth.basicPassword?.trim() ?? '';
+ if (password.length === 0) {
+ throw new Error(`${label} auth mode "basic" requires basicPassword`);
+ }
+ let user = auth.basicUser?.trim() ?? '';
+ if (user.length === 0) {
+ user = tenantId;
+ }
+ if (user.length === 0) {
+ throw new Error(`${label} auth mode "basic" requires basicUser or tenantId`);
+ }
+ const result: Record = { ...(out ?? {}) };
+ if (!hasHeaderKey(result, authorizationHeaderName)) {
+ const encoded = new TextEncoder().encode(`${user}:${password}`);
+ result[authorizationHeaderName] = 'Basic ' + btoa(String.fromCharCode(...encoded));
+ }
+ if (tenantId.length > 0 && !hasHeaderKey(result, tenantHeaderName)) {
+ result[tenantHeaderName] = tenantId;
+ }
+ return result;
+ }
+
throw new Error(`unsupported ${label} auth mode: ${auth.mode}`);
}
diff --git a/js/src/context.ts b/js/src/context.ts
new file mode 100644
index 0000000..4aee505
--- /dev/null
+++ b/js/src/context.ts
@@ -0,0 +1,75 @@
+import { AsyncLocalStorage } from 'node:async_hooks';
+
+type SigilContextValues = {
+ conversationId?: string;
+ conversationTitle?: string;
+ userId?: string;
+ agentName?: string;
+ agentVersion?: string;
+};
+
+const storage = new AsyncLocalStorage();
+
+export function withConversationId(conversationId: string, callback: () => T): T {
+ return runWithContext({ conversationId }, callback);
+}
+
+export function withConversationTitle(conversationTitle: string, callback: () => T): T {
+ return runWithContext({ conversationTitle }, callback);
+}
+
+export function withUserId(userId: string, callback: () => T): T {
+ return runWithContext({ userId }, callback);
+}
+
+export function withAgentName(agentName: string, callback: () => T): T {
+ return runWithContext({ agentName }, callback);
+}
+
+export function withAgentVersion(agentVersion: string, callback: () => T): T {
+ return runWithContext({ agentVersion }, callback);
+}
+
+export function conversationIdFromContext(): string | undefined {
+ return normalizedString(storage.getStore()?.conversationId);
+}
+
+export function conversationTitleFromContext(): string | undefined {
+ return normalizedString(storage.getStore()?.conversationTitle);
+}
+
+export function userIdFromContext(): string | undefined {
+ return normalizedString(storage.getStore()?.userId);
+}
+
+export function agentNameFromContext(): string | undefined {
+ return normalizedString(storage.getStore()?.agentName);
+}
+
+export function agentVersionFromContext(): string | undefined {
+ return normalizedString(storage.getStore()?.agentVersion);
+}
+
+function runWithContext(nextValues: SigilContextValues, callback: () => T): T {
+ const currentValues = storage.getStore() ?? {};
+ const mergedValues: SigilContextValues = { ...currentValues };
+
+ for (const [key, value] of Object.entries(nextValues)) {
+ const normalized = normalizedString(value);
+ if (normalized === undefined) {
+ delete mergedValues[key as keyof SigilContextValues];
+ continue;
+ }
+ mergedValues[key as keyof SigilContextValues] = normalized;
+ }
+
+ return storage.run(mergedValues, callback);
+}
+
+function normalizedString(value: string | undefined): string | undefined {
+ if (value === undefined) {
+ return undefined;
+ }
+ const trimmed = value.trim();
+ return trimmed.length > 0 ? trimmed : undefined;
+}
diff --git a/js/src/index.ts b/js/src/index.ts
index a573479..64f7d7b 100644
--- a/js/src/index.ts
+++ b/js/src/index.ts
@@ -1,5 +1,17 @@
export { SigilClient } from './client.js';
export { defaultConfig } from './config.js';
+export {
+ agentNameFromContext,
+ agentVersionFromContext,
+ conversationIdFromContext,
+ conversationTitleFromContext,
+ userIdFromContext,
+ withAgentName,
+ withAgentVersion,
+ withConversationId,
+ withConversationTitle,
+ withUserId,
+} from './context.js';
export type {
ApiConfig,
Artifact,
diff --git a/js/src/providers/openai.ts b/js/src/providers/openai.ts
index 1ee8580..50a1f23 100644
--- a/js/src/providers/openai.ts
+++ b/js/src/providers/openai.ts
@@ -503,7 +503,7 @@ function mapChatRequestMessages(request: ChatCreateRequest | ChatStreamRequest):
const normalizedRole: Message['role'] = role === 'assistant' || role === 'tool' ? role : 'user';
const message: Message = { role: normalizedRole };
- if (content.length > 0) {
+ if (normalizedRole !== 'tool' && content.length > 0) {
message.content = content;
}
@@ -511,6 +511,20 @@ function mapChatRequestMessages(request: ChatCreateRequest | ChatStreamRequest):
message.name = rawMessage.name;
}
+ if (normalizedRole === 'tool') {
+ const toolResult = mapToolResultMessage(
+ rawMessage.content,
+ rawMessage.tool_call_id ?? rawMessage.toolCallId ?? rawMessage.id,
+ rawMessage.name,
+ rawMessage.is_error,
+ 'tool_result'
+ );
+ if (toolResult) {
+ input.push(toolResult);
+ }
+ continue;
+ }
+
if (normalizedRole === 'assistant' && Array.isArray(rawMessage.tool_calls)) {
const parts = mapChatToolCallParts(rawMessage.tool_calls);
if (parts.length > 0) {
@@ -760,10 +774,15 @@ function mapResponsesRequest(request: ResponsesCreateRequest | ResponsesStreamRe
}
if (itemType === 'function_call_output') {
- const outputValue = rawItem.output;
- const content = typeof outputValue === 'string' ? outputValue : jsonString(outputValue);
- if (content.length > 0) {
- input.push({ role: 'tool', content });
+ const toolResult = mapToolResultMessage(
+ rawItem.output,
+ rawItem.call_id ?? rawItem.callId,
+ rawItem.name,
+ rawItem.is_error,
+ 'tool_result'
+ );
+ if (toolResult) {
+ input.push(toolResult);
}
continue;
}
@@ -899,11 +918,15 @@ function mapResponsesOutputItems(value: unknown): Message[] {
}
if (itemType === 'function_call_output') {
- const content = typeof rawItem.output === 'string'
- ? rawItem.output
- : jsonString(rawItem.output);
- if (content.length > 0) {
- output.push({ role: 'tool', content });
+ const toolResult = mapToolResultMessage(
+ rawItem.output,
+ rawItem.call_id ?? rawItem.callId,
+ rawItem.name,
+ rawItem.is_error,
+ 'tool_result'
+ );
+ if (toolResult) {
+ output.push(toolResult);
}
continue;
}
@@ -1165,6 +1188,40 @@ function openAIThinkingBudget(reasoning: unknown): number | undefined {
return undefined;
}
+function mapToolResultMessage(
+ value: unknown,
+ toolCallId: unknown,
+ name: unknown,
+ isError: unknown,
+ providerType: string
+): Message | undefined {
+ const content = extractText(value);
+ const contentJSON = jsonString(value);
+ const renderedContent = content.length > 0 ? content : contentJSON;
+
+ if (renderedContent.length === 0) {
+ return undefined;
+ }
+
+ return {
+ role: 'tool',
+ content: renderedContent,
+ parts: [
+ {
+ type: 'tool_result',
+ toolResult: {
+ toolCallId: typeof toolCallId === 'string' && toolCallId.trim().length > 0 ? toolCallId : undefined,
+ name: typeof name === 'string' && name.trim().length > 0 ? name : undefined,
+ content: renderedContent,
+ contentJSON,
+ isError: typeof isError === 'boolean' ? isError : undefined,
+ },
+ metadata: { providerType },
+ },
+ ],
+ };
+}
+
function metadataWithThinkingBudget(
metadata: Record | undefined,
thinkingBudget: number | undefined
diff --git a/js/src/types.ts b/js/src/types.ts
index 35582f0..0e3210a 100644
--- a/js/src/types.ts
+++ b/js/src/types.ts
@@ -5,13 +5,17 @@ export type GenerationExportProtocol = 'grpc' | 'http' | 'none';
/** Generation execution mode. */
export type GenerationMode = 'SYNC' | 'STREAM';
/** Supported auth modes for transport exports. */
-export type ExportAuthMode = 'none' | 'tenant' | 'bearer';
+export type ExportAuthMode = 'none' | 'tenant' | 'bearer' | 'basic';
/** Per-export auth configuration. */
export interface ExportAuthConfig {
mode: ExportAuthMode;
tenantId?: string;
bearerToken?: string;
+ /** Username for basic auth. When empty, tenantId is used. */
+ basicUser?: string;
+ /** Password/token for basic auth. */
+ basicPassword?: string;
}
/** Generation exporter runtime configuration. */
@@ -238,6 +242,8 @@ export interface Artifact {
export interface GenerationStart {
id?: string;
conversationId?: string;
+ conversationTitle?: string;
+ userId?: string;
agentName?: string;
agentVersion?: string;
mode?: GenerationMode;
@@ -258,6 +264,8 @@ export interface GenerationStart {
/** Final generation result fields. */
export interface GenerationResult {
conversationId?: string;
+ conversationTitle?: string;
+ userId?: string;
agentName?: string;
agentVersion?: string;
operationName?: string;
@@ -304,6 +312,8 @@ export interface EmbeddingResult {
export interface Generation {
id: string;
conversationId?: string;
+ conversationTitle?: string;
+ userId?: string;
agentName?: string;
agentVersion?: string;
mode: GenerationMode;
@@ -339,8 +349,13 @@ export interface ToolExecutionStart {
toolType?: string;
toolDescription?: string;
conversationId?: string;
+ conversationTitle?: string;
agentName?: string;
agentVersion?: string;
+ /** The model that requested the tool call (e.g. "gpt-5"). */
+ requestModel?: string;
+ /** The provider that served the model (e.g. "openai"). */
+ requestProvider?: string;
includeContent?: boolean;
startedAt?: Date;
}
@@ -359,8 +374,11 @@ export interface ToolExecution {
toolType?: string;
toolDescription?: string;
conversationId?: string;
+ conversationTitle?: string;
agentName?: string;
agentVersion?: string;
+ requestModel?: string;
+ requestProvider?: string;
includeContent: boolean;
startedAt: Date;
completedAt: Date;
diff --git a/js/src/utils.ts b/js/src/utils.ts
index 44d28e8..91f15b0 100644
--- a/js/src/utils.ts
+++ b/js/src/utils.ts
@@ -5,11 +5,13 @@ import type {
Generation,
GenerationMode,
GenerationResult,
+ GenerationStart,
Message,
MessagePart,
ModelRef,
ToolDefinition,
ToolExecution,
+ ToolExecutionStart,
ToolExecutionResult,
} from './types.js';
@@ -159,6 +161,17 @@ export function cloneGenerationResult(result: GenerationResult): GenerationResul
};
}
+export function cloneGenerationStart(start: GenerationStart): GenerationStart {
+ return {
+ ...start,
+ model: cloneModelRef(start.model),
+ tools: start.tools?.map(cloneToolDefinition),
+ tags: start.tags ? { ...start.tags } : undefined,
+ metadata: start.metadata ? { ...start.metadata } : undefined,
+ startedAt: start.startedAt ? new Date(start.startedAt) : undefined,
+ };
+}
+
export function cloneEmbeddingStart(start: EmbeddingStart): EmbeddingStart {
return {
...start,
@@ -184,6 +197,13 @@ export function cloneToolExecution(toolExecution: ToolExecution): ToolExecution
};
}
+export function cloneToolExecutionStart(start: ToolExecutionStart): ToolExecutionStart {
+ return {
+ ...start,
+ startedAt: start.startedAt ? new Date(start.startedAt) : undefined,
+ };
+}
+
export function cloneToolExecutionResult(result: ToolExecutionResult): ToolExecutionResult {
return {
...result,
diff --git a/js/test/client.auth.config.test.mjs b/js/test/client.auth.config.test.mjs
index c0984d3..852c01a 100644
--- a/js/test/client.auth.config.test.mjs
+++ b/js/test/client.auth.config.test.mjs
@@ -15,3 +15,135 @@ test('invalid generation auth config throws at client init', () => {
/requires tenantId/
);
});
+
+test('basic auth mode injects Authorization and X-Scope-OrgID headers', () => {
+ const client = new SigilClient({
+ generationExport: {
+ auth: {
+ mode: 'basic',
+ tenantId: '42',
+ basicPassword: 'secret',
+ },
+ },
+ });
+
+ const expected = 'Basic ' + btoa('42:secret');
+ assert.equal(client.config.generationExport.headers?.['Authorization'], expected);
+ assert.equal(client.config.generationExport.headers?.['X-Scope-OrgID'], '42');
+ client.shutdown();
+});
+
+test('basic auth mode uses basicUser over tenantId for credential', () => {
+ const client = new SigilClient({
+ generationExport: {
+ auth: {
+ mode: 'basic',
+ tenantId: '42',
+ basicUser: 'probe-user',
+ basicPassword: 'secret',
+ },
+ },
+ });
+
+ const expected = 'Basic ' + btoa('probe-user:secret');
+ assert.equal(client.config.generationExport.headers?.['Authorization'], expected);
+ assert.equal(client.config.generationExport.headers?.['X-Scope-OrgID'], '42');
+ client.shutdown();
+});
+
+test('basic auth mode requires basicPassword', () => {
+ assert.throws(
+ () =>
+ new SigilClient({
+ generationExport: {
+ auth: {
+ mode: 'basic',
+ tenantId: '42',
+ },
+ },
+ }),
+ /requires basicPassword/
+ );
+});
+
+test('basic auth mode requires basicUser or tenantId', () => {
+ assert.throws(
+ () =>
+ new SigilClient({
+ generationExport: {
+ auth: {
+ mode: 'basic',
+ basicPassword: 'secret',
+ },
+ },
+ }),
+ /requires basicUser or tenantId/
+ );
+});
+
+test('none auth mode rejects basicUser', () => {
+ assert.throws(
+ () =>
+ new SigilClient({
+ generationExport: {
+ auth: {
+ mode: 'none',
+ basicUser: 'user',
+ },
+ },
+ }),
+ /does not allow credentials/
+ );
+});
+
+test('none auth mode rejects basicPassword', () => {
+ assert.throws(
+ () =>
+ new SigilClient({
+ generationExport: {
+ auth: {
+ mode: 'none',
+ basicPassword: 'secret',
+ },
+ },
+ }),
+ /does not allow credentials/
+ );
+});
+
+test('basic auth handles non-ASCII credentials via UTF-8 encoding', () => {
+ const client = new SigilClient({
+ generationExport: {
+ auth: {
+ mode: 'basic',
+ basicUser: 'ユーザー',
+ basicPassword: 'パスワード',
+ },
+ },
+ });
+
+ const encoded = Buffer.from('ユーザー:パスワード', 'utf-8').toString('base64');
+ const expected = 'Basic ' + encoded;
+ assert.equal(client.config.generationExport.headers?.['Authorization'], expected);
+ client.shutdown();
+});
+
+test('basic auth explicit headers win over auth-derived headers', () => {
+ const client = new SigilClient({
+ generationExport: {
+ headers: {
+ Authorization: 'Basic override',
+ 'X-Scope-OrgID': 'override-tenant',
+ },
+ auth: {
+ mode: 'basic',
+ tenantId: '42',
+ basicPassword: 'secret',
+ },
+ },
+ });
+
+ assert.equal(client.config.generationExport.headers?.['Authorization'], 'Basic override');
+ assert.equal(client.config.generationExport.headers?.['X-Scope-OrgID'], 'override-tenant');
+ client.shutdown();
+});
diff --git a/js/test/client.spans.test.mjs b/js/test/client.spans.test.mjs
index 264041e..60987fb 100644
--- a/js/test/client.spans.test.mjs
+++ b/js/test/client.spans.test.mjs
@@ -105,6 +105,77 @@ test('generation result fields override seed and update span operation name', as
}
});
+test('generation normalization trims only title and user fields', async () => {
+ const harness = newHarness();
+
+ try {
+ const recorder = harness.client.startGeneration({
+ conversationId: ' conv-seed ',
+ conversationTitle: ' title-seed ',
+ userId: ' user-seed ',
+ agentName: ' agent-seed ',
+ agentVersion: ' v-seed ',
+ model: { provider: 'openai', name: 'gpt-5' },
+ });
+ recorder.setResult({
+ conversationId: ' conv-result ',
+ conversationTitle: ' title-result ',
+ userId: ' user-result ',
+ agentName: ' agent-result ',
+ agentVersion: ' v-result ',
+ });
+ recorder.end();
+ assert.equal(recorder.getError(), undefined);
+
+ const generation = singleGeneration(harness.client);
+ assert.equal(generation.conversationId, ' conv-result ');
+ assert.equal(generation.conversationTitle, 'title-result');
+ assert.equal(generation.userId, 'user-result');
+ assert.equal(generation.agentName, ' agent-result ');
+ assert.equal(generation.agentVersion, ' v-result ');
+ assert.equal(generation.metadata?.['sigil.conversation.title'], 'title-result');
+ assert.equal(generation.metadata?.['sigil.user.id'], 'user-result');
+
+ const span = singleGenerationSpan(harness.spanExporter);
+ assert.equal(span.attributes['gen_ai.conversation.id'], ' conv-result ');
+ assert.equal(span.attributes['sigil.conversation.title'], 'title-result');
+ assert.equal(span.attributes['user.id'], 'user-result');
+ assert.equal(span.attributes['gen_ai.agent.name'], ' agent-result ');
+ assert.equal(span.attributes['gen_ai.agent.version'], ' v-result ');
+ } finally {
+ await shutdownHarness(harness);
+ }
+});
+
+test('generation span reflects metadata fallback title and user id after normalization', async () => {
+ const harness = newHarness();
+
+ try {
+ const recorder = harness.client.startGeneration({
+ model: { provider: 'openai', name: 'gpt-5' },
+ metadata: {
+ 'sigil.conversation.title': ' Meta title ',
+ 'user.id': ' legacy-user ',
+ },
+ });
+ recorder.setResult({});
+ recorder.end();
+ assert.equal(recorder.getError(), undefined);
+
+ const generation = singleGeneration(harness.client);
+ assert.equal(generation.conversationTitle, 'Meta title');
+ assert.equal(generation.userId, 'legacy-user');
+ assert.equal(generation.metadata?.['sigil.conversation.title'], 'Meta title');
+ assert.equal(generation.metadata?.['sigil.user.id'], 'legacy-user');
+
+ const span = singleGenerationSpan(harness.spanExporter);
+ assert.equal(span.attributes['sigil.conversation.title'], 'Meta title');
+ assert.equal(span.attributes['user.id'], 'legacy-user');
+ } finally {
+ await shutdownHarness(harness);
+ }
+});
+
test('generation callError sets metadata and provider_call_error span status', async () => {
const harness = newHarness();
@@ -249,6 +320,8 @@ test('tool execution includeContent controls argument/result attributes', async
conversationId: 'conv-tool',
agentName: 'agent-tool',
agentVersion: 'v-tool',
+ requestProvider: 'openai',
+ requestModel: 'gpt-5',
});
withContent.setResult({
arguments: { city: 'Paris' },
@@ -280,6 +353,8 @@ test('tool execution includeContent controls argument/result attributes', async
assert.equal(contentSpan.attributes['gen_ai.conversation.id'], 'conv-tool');
assert.equal(contentSpan.attributes['gen_ai.agent.name'], 'agent-tool');
assert.equal(contentSpan.attributes['gen_ai.agent.version'], 'v-tool');
+ assert.equal(contentSpan.attributes['gen_ai.provider.name'], 'openai');
+ assert.equal(contentSpan.attributes['gen_ai.request.model'], 'gpt-5');
assert.equal(contentSpan.attributes['sigil.sdk.name'], 'sdk-js');
assert.equal(noContentSpan.attributes['gen_ai.tool.call.arguments'], undefined);
assert.equal(noContentSpan.attributes['gen_ai.tool.call.result'], undefined);
diff --git a/js/test/conformance.test.mjs b/js/test/conformance.test.mjs
new file mode 100644
index 0000000..cdd33cf
--- /dev/null
+++ b/js/test/conformance.test.mjs
@@ -0,0 +1,716 @@
+import assert from 'node:assert/strict';
+import { createServer } from 'node:http';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import test from 'node:test';
+import * as grpc from '@grpc/grpc-js';
+import * as protoLoader from '@grpc/proto-loader';
+import { AggregationTemporality, InMemoryMetricExporter, MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
+import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
+import {
+ SigilClient,
+ defaultConfig,
+ withAgentName,
+ withAgentVersion,
+ withConversationTitle,
+ withUserId,
+} from '../.test-dist/index.js';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+const protoPath = join(__dirname, '../proto/sigil/v1/generation_ingest.proto');
+const protoLoadOptions = {
+ keepCase: false,
+ longs: String,
+ enums: String,
+ defaults: false,
+ oneofs: true,
+};
+
+test('conformance sync roundtrip semantics', async () => {
+ const env = await createConformanceEnv();
+
+ try {
+ const recorder = env.client.startGeneration({
+ id: 'gen-roundtrip',
+ conversationId: 'conv-roundtrip',
+ conversationTitle: 'Roundtrip conversation',
+ userId: 'user-roundtrip',
+ agentName: 'agent-roundtrip',
+ agentVersion: 'v-roundtrip',
+ model: { provider: 'openai', name: 'gpt-5' },
+ maxTokens: 256,
+ temperature: 0.2,
+ topP: 0.9,
+ toolChoice: 'required',
+ thinkingEnabled: false,
+ tools: [{ name: 'weather', description: 'Get weather', type: 'function' }],
+ tags: { tenant: 'dev' },
+ metadata: { trace: 'roundtrip' },
+ });
+ recorder.setResult({
+ responseId: 'resp-roundtrip',
+ responseModel: 'gpt-5-2026',
+ input: [{ role: 'user', parts: [{ type: 'text', text: 'hello' }] }],
+ output: [
+ {
+ role: 'assistant',
+ parts: [
+ { type: 'thinking', thinking: 'reasoning' },
+ {
+ type: 'tool_call',
+ toolCall: {
+ id: 'call-1',
+ name: 'weather',
+ inputJSON: '{"city":"Paris"}',
+ },
+ },
+ ],
+ },
+ {
+ role: 'tool',
+ parts: [
+ {
+ type: 'tool_result',
+ toolResult: {
+ toolCallId: 'call-1',
+ name: 'weather',
+ content: 'sunny',
+ contentJSON: '{"temp_c":18}',
+ },
+ },
+ ],
+ },
+ ],
+ usage: {
+ inputTokens: 12,
+ outputTokens: 7,
+ totalTokens: 19,
+ cacheReadInputTokens: 2,
+ cacheWriteInputTokens: 1,
+ cacheCreationInputTokens: 3,
+ reasoningTokens: 4,
+ },
+ stopReason: 'stop',
+ tags: { region: 'eu' },
+ metadata: { result: 'ok' },
+ artifacts: [
+ { type: 'request', name: 'request', mimeType: 'application/json', payload: '{"prompt":"hello"}' },
+ { type: 'response', name: 'response', mimeType: 'application/json', payload: '{"text":"sunny"}' },
+ ],
+ });
+ recorder.end();
+ assert.equal(recorder.getError(), undefined);
+
+ await env.client.shutdown();
+ const generation = env.singleGeneration();
+ const span = env.latestGenerationSpan();
+ const metricNames = await env.metricNames();
+
+ assert.equal(generation.mode, 'GENERATION_MODE_SYNC');
+ assert.equal(generation.operationName, 'generateText');
+ assert.equal(generation.conversationId, 'conv-roundtrip');
+ assert.equal(generation.agentName, 'agent-roundtrip');
+ assert.equal(generation.agentVersion, 'v-roundtrip');
+ assert.equal(generation.traceId, span.spanContext().traceId);
+ assert.equal(generation.spanId, span.spanContext().spanId);
+ assert.equal(generation.metadata?.fields?.['sigil.conversation.title']?.stringValue, 'Roundtrip conversation');
+ assert.equal(generation.metadata?.fields?.['sigil.user.id']?.stringValue, 'user-roundtrip');
+ assert.equal(generation.input?.[0]?.parts?.[0]?.text, 'hello');
+ assert.equal(generation.output?.[0]?.parts?.[0]?.thinking, 'reasoning');
+ assert.equal(generation.output?.[0]?.parts?.[1]?.toolCall?.name, 'weather');
+ assert.equal(generation.output?.[1]?.parts?.[0]?.toolResult?.content, 'sunny');
+ assert.equal(Number(generation.maxTokens), 256);
+ assert.equal(generation.temperature, 0.2);
+ assert.equal(generation.topP, 0.9);
+ assert.equal(generation.toolChoice, 'required');
+ assert.equal(generation.thinkingEnabled, false);
+ assert.equal(Number(generation.usage?.inputTokens ?? 0), 12);
+ assert.equal(Number(generation.usage?.outputTokens ?? 0), 7);
+ assert.equal(Number(generation.usage?.totalTokens ?? 0), 19);
+ assert.equal(Number(generation.usage?.cacheReadInputTokens ?? 0), 2);
+ assert.equal(Number(generation.usage?.cacheWriteInputTokens ?? 0), 1);
+ assert.equal(Number(generation.usage?.reasoningTokens ?? 0), 4);
+ assert.equal(generation.stopReason, 'stop');
+ assert.equal(generation.tags?.tenant, 'dev');
+ assert.equal(generation.tags?.region, 'eu');
+ assert.equal((generation.rawArtifacts ?? []).length, 2);
+ assert.equal(span.attributes['gen_ai.operation.name'], 'generateText');
+ assert.equal(span.attributes['sigil.conversation.title'], 'Roundtrip conversation');
+ assert.equal(span.attributes['user.id'], 'user-roundtrip');
+ assert.ok(metricNames.includes('gen_ai.client.operation.duration'));
+ assert.ok(metricNames.includes('gen_ai.client.token.usage'));
+ assert.ok(!metricNames.includes('gen_ai.client.time_to_first_token'));
+ } finally {
+ await env.close();
+ }
+});
+
+for (const testCase of [
+ { name: 'explicit wins', startTitle: 'Explicit', contextTitle: 'Context', metadataTitle: 'Meta', expected: 'Explicit' },
+ { name: 'context fallback', startTitle: '', contextTitle: 'Context', metadataTitle: '', expected: 'Context' },
+ { name: 'metadata fallback', startTitle: '', contextTitle: '', metadataTitle: 'Meta', expected: 'Meta' },
+ { name: 'whitespace trimmed', startTitle: ' Padded ', contextTitle: '', metadataTitle: '', expected: 'Padded' },
+ { name: 'whitespace omitted', startTitle: ' ', contextTitle: '', metadataTitle: '', expected: '' },
+]) {
+ test(`conformance conversation title semantics: ${testCase.name}`, async () => {
+ const env = await createConformanceEnv();
+
+ try {
+ await runWithMaybeContext(testCase.contextTitle, withConversationTitle, async () => {
+ const recorder = env.client.startGeneration({
+ model: { provider: 'openai', name: 'gpt-5' },
+ conversationTitle: testCase.startTitle,
+ metadata: testCase.metadataTitle.length > 0 ? { 'sigil.conversation.title': testCase.metadataTitle } : undefined,
+ });
+ recorder.setResult({});
+ recorder.end();
+ assert.equal(recorder.getError(), undefined);
+ });
+
+ await env.client.shutdown();
+ const generation = env.singleGeneration();
+ const span = env.latestGenerationSpan();
+ if (testCase.expected.length === 0) {
+ assert.equal(generation.metadata?.fields?.['sigil.conversation.title'], undefined);
+ assert.equal(span.attributes['sigil.conversation.title'], undefined);
+ return;
+ }
+
+ assert.equal(generation.metadata?.fields?.['sigil.conversation.title']?.stringValue, testCase.expected);
+ assert.equal(span.attributes['sigil.conversation.title'], testCase.expected);
+ } finally {
+ await env.close();
+ }
+ });
+}
+
+for (const testCase of [
+ { name: 'explicit wins', startUserId: 'explicit', contextUserId: 'ctx', canonicalUserId: 'canonical', legacyUserId: 'legacy', expected: 'explicit' },
+ { name: 'context fallback', startUserId: '', contextUserId: 'ctx', canonicalUserId: '', legacyUserId: '', expected: 'ctx' },
+ { name: 'canonical metadata', startUserId: '', contextUserId: '', canonicalUserId: 'canonical', legacyUserId: '', expected: 'canonical' },
+ { name: 'legacy metadata', startUserId: '', contextUserId: '', canonicalUserId: '', legacyUserId: 'legacy', expected: 'legacy' },
+ { name: 'canonical beats legacy', startUserId: '', contextUserId: '', canonicalUserId: 'canonical', legacyUserId: 'legacy', expected: 'canonical' },
+ { name: 'whitespace trimmed', startUserId: ' padded ', contextUserId: '', canonicalUserId: '', legacyUserId: '', expected: 'padded' },
+]) {
+ test(`conformance user id semantics: ${testCase.name}`, async () => {
+ const env = await createConformanceEnv();
+
+ try {
+ await runWithMaybeContext(testCase.contextUserId, withUserId, async () => {
+ const metadata = {};
+ if (testCase.canonicalUserId.length > 0) {
+ metadata['sigil.user.id'] = testCase.canonicalUserId;
+ }
+ if (testCase.legacyUserId.length > 0) {
+ metadata['user.id'] = testCase.legacyUserId;
+ }
+
+ const recorder = env.client.startGeneration({
+ model: { provider: 'openai', name: 'gpt-5' },
+ userId: testCase.startUserId,
+ metadata,
+ });
+ recorder.setResult({});
+ recorder.end();
+ assert.equal(recorder.getError(), undefined);
+ });
+
+ await env.client.shutdown();
+ const generation = env.singleGeneration();
+ const span = env.latestGenerationSpan();
+ assert.equal(generation.metadata?.fields?.['sigil.user.id']?.stringValue, testCase.expected);
+ assert.equal(span.attributes['user.id'], testCase.expected);
+ } finally {
+ await env.close();
+ }
+ });
+}
+
+for (const testCase of [
+ {
+ name: 'explicit fields',
+ startName: 'agent-explicit',
+ startVersion: 'v1.2.3',
+ contextName: '',
+ contextVersion: '',
+ resultName: '',
+ resultVersion: '',
+ expectedName: 'agent-explicit',
+ expectedVersion: 'v1.2.3',
+ },
+ {
+ name: 'context fallback',
+ startName: '',
+ startVersion: '',
+ contextName: 'agent-context',
+ contextVersion: 'v-context',
+ resultName: '',
+ resultVersion: '',
+ expectedName: 'agent-context',
+ expectedVersion: 'v-context',
+ },
+ {
+ name: 'result override',
+ startName: 'agent-seed',
+ startVersion: 'v-seed',
+ contextName: '',
+ contextVersion: '',
+ resultName: 'agent-result',
+ resultVersion: 'v-result',
+ expectedName: 'agent-result',
+ expectedVersion: 'v-result',
+ },
+ {
+ name: 'empty omission',
+ startName: '',
+ startVersion: '',
+ contextName: '',
+ contextVersion: '',
+ resultName: '',
+ resultVersion: '',
+ expectedName: '',
+ expectedVersion: '',
+ },
+]) {
+ test(`conformance agent identity semantics: ${testCase.name}`, async () => {
+ const env = await createConformanceEnv();
+
+ try {
+ await runWithMaybeContext(testCase.contextName, withAgentName, async () => {
+ await runWithMaybeContext(testCase.contextVersion, withAgentVersion, async () => {
+ const recorder = env.client.startGeneration({
+ model: { provider: 'openai', name: 'gpt-5' },
+ agentName: testCase.startName,
+ agentVersion: testCase.startVersion,
+ });
+ recorder.setResult({
+ agentName: testCase.resultName,
+ agentVersion: testCase.resultVersion,
+ });
+ recorder.end();
+ assert.equal(recorder.getError(), undefined);
+ });
+ });
+
+ await env.client.shutdown();
+ const generation = env.singleGeneration();
+ const span = env.latestGenerationSpan();
+ assert.equal(generation.agentName ?? '', testCase.expectedName);
+ assert.equal(generation.agentVersion ?? '', testCase.expectedVersion);
+ assert.equal(span.attributes['gen_ai.agent.name'], testCase.expectedName || undefined);
+ assert.equal(span.attributes['gen_ai.agent.version'], testCase.expectedVersion || undefined);
+ } finally {
+ await env.close();
+ }
+ });
+}
+
+test('conformance streaming telemetry semantics', async () => {
+ const env = await createConformanceEnv();
+
+ try {
+ const startedAt = new Date('2026-03-12T09:00:00Z');
+ const recorder = env.client.startStreamingGeneration({
+ model: { provider: 'openai', name: 'gpt-5' },
+ startedAt,
+ });
+ recorder.setFirstTokenAt(new Date('2026-03-12T09:00:00.250Z'));
+ recorder.setResult({
+ output: [{ role: 'assistant', parts: [{ type: 'text', text: 'Hello world' }] }],
+ usage: { inputTokens: 4, outputTokens: 3, totalTokens: 7 },
+ startedAt,
+ completedAt: new Date('2026-03-12T09:00:01Z'),
+ });
+ recorder.end();
+ assert.equal(recorder.getError(), undefined);
+
+ await env.client.shutdown();
+ const generation = env.singleGeneration();
+ const span = env.latestGenerationSpan();
+ const metricNames = await env.metricNames();
+
+ assert.equal(generation.mode, 'GENERATION_MODE_STREAM');
+ assert.equal(generation.operationName, 'streamText');
+ assert.equal(generation.output?.[0]?.parts?.[0]?.text, 'Hello world');
+ assert.equal(span.name, 'streamText gpt-5');
+ assert.ok(metricNames.includes('gen_ai.client.operation.duration'));
+ assert.ok(metricNames.includes('gen_ai.client.time_to_first_token'));
+ } finally {
+ await env.close();
+ }
+});
+
+test('conformance tool execution semantics', async () => {
+ const env = await createConformanceEnv();
+
+ try {
+ await runWithMaybeContext('Context title', withConversationTitle, async () => {
+ await runWithMaybeContext('agent-context', withAgentName, async () => {
+ await runWithMaybeContext('v-context', withAgentVersion, async () => {
+ const recorder = env.client.startToolExecution({
+ toolName: 'weather',
+ toolCallId: 'call-weather-1',
+ toolType: 'function',
+ includeContent: true,
+ });
+ recorder.setResult({
+ arguments: { city: 'Paris' },
+ result: { forecast: 'sunny' },
+ });
+ recorder.end();
+ assert.equal(recorder.getError(), undefined);
+ });
+ });
+ });
+
+ await env.client.shutdown();
+ const span = env.latestSpanByOperation('execute_tool');
+ const metricNames = await env.metricNames();
+
+ assert.equal(env.receivedRequests.length, 0);
+ assert.equal(span.name, 'execute_tool weather');
+ assert.equal(span.attributes['gen_ai.operation.name'], 'execute_tool');
+ assert.equal(span.attributes['gen_ai.tool.name'], 'weather');
+ assert.equal(span.attributes['gen_ai.tool.call.id'], 'call-weather-1');
+ assert.equal(span.attributes['gen_ai.tool.type'], 'function');
+ assert.match(String(span.attributes['gen_ai.tool.call.arguments'] ?? ''), /Paris/);
+ assert.match(String(span.attributes['gen_ai.tool.call.result'] ?? ''), /sunny/);
+ assert.equal(span.attributes['sigil.conversation.title'], 'Context title');
+ assert.equal(span.attributes['gen_ai.agent.name'], 'agent-context');
+ assert.equal(span.attributes['gen_ai.agent.version'], 'v-context');
+ assert.ok(metricNames.includes('gen_ai.client.operation.duration'));
+ assert.ok(!metricNames.includes('gen_ai.client.time_to_first_token'));
+ } finally {
+ await env.close();
+ }
+});
+
+test('conformance embedding semantics', async () => {
+ const env = await createConformanceEnv();
+
+ try {
+ await runWithMaybeContext('agent-context', withAgentName, async () => {
+ await runWithMaybeContext('v-context', withAgentVersion, async () => {
+ const recorder = env.client.startEmbedding({
+ model: { provider: 'openai', name: 'text-embedding-3-small' },
+ dimensions: 512,
+ });
+ recorder.setResult({
+ inputCount: 2,
+ inputTokens: 8,
+ inputTexts: ['hello', 'world'],
+ responseModel: 'text-embedding-3-small',
+ dimensions: 512,
+ });
+ recorder.end();
+ assert.equal(recorder.getError(), undefined);
+ });
+ });
+
+ await env.client.shutdown();
+ const span = env.latestSpanByOperation('embeddings');
+ const metricNames = await env.metricNames();
+
+ assert.equal(env.receivedRequests.length, 0);
+ assert.equal(span.name, 'embeddings text-embedding-3-small');
+ assert.equal(span.attributes['gen_ai.operation.name'], 'embeddings');
+ assert.equal(span.attributes['gen_ai.agent.name'], 'agent-context');
+ assert.equal(span.attributes['gen_ai.agent.version'], 'v-context');
+ assert.equal(span.attributes['gen_ai.embeddings.input_count'], 2);
+ assert.equal(span.attributes['gen_ai.embeddings.dimension.count'], 512);
+ assert.equal(span.attributes['gen_ai.response.model'], 'text-embedding-3-small');
+ assert.ok(metricNames.includes('gen_ai.client.operation.duration'));
+ assert.ok(metricNames.includes('gen_ai.client.token.usage'));
+ assert.ok(!metricNames.includes('gen_ai.client.time_to_first_token'));
+ assert.ok(!metricNames.includes('gen_ai.client.tool_calls_per_operation'));
+ } finally {
+ await env.close();
+ }
+});
+
+test('conformance validation and provider call error semantics', async () => {
+ const env = await createConformanceEnv();
+
+ try {
+ const invalid = env.client.startGeneration({
+ model: { provider: 'anthropic', name: 'claude-sonnet-4-5' },
+ });
+ invalid.setResult({
+ input: [
+ {
+ role: 'user',
+ parts: [{ type: 'tool_call', toolCall: { name: 'weather' } }],
+ },
+ ],
+ });
+ invalid.end();
+
+ assert.match(invalid.getError()?.message ?? '', /tool_call only allowed for assistant role/);
+ assert.equal(env.receivedRequests.length, 0);
+ assert.equal(env.latestGenerationSpan().attributes['error.type'], 'validation_error');
+
+ const callError = env.client.startGeneration({
+ model: { provider: 'openai', name: 'gpt-5' },
+ });
+ callError.setCallError(new Error('provider unavailable'));
+ callError.setResult({});
+ callError.end();
+ assert.equal(callError.getError(), undefined);
+
+ await env.client.shutdown();
+ const generation = env.singleGeneration();
+ const span = env.latestGenerationSpan();
+ assert.equal(generation.callError, 'provider unavailable');
+ assert.equal(generation.metadata?.fields?.call_error?.stringValue, 'provider unavailable');
+ assert.equal(span.attributes['error.type'], 'provider_call_error');
+ } finally {
+ await env.close();
+ }
+});
+
+test('conformance rating submission semantics', async () => {
+ const env = await createConformanceEnv();
+
+ try {
+ const response = await env.client.submitConversationRating('conv-rating', {
+ ratingId: 'rat-1',
+ rating: 'CONVERSATION_RATING_VALUE_BAD',
+ comment: 'wrong answer',
+ metadata: { channel: 'assistant' },
+ });
+
+ assert.equal(env.ratingPath, '/api/v1/conversations/conv-rating/ratings');
+ assert.deepEqual(env.ratingPayload, {
+ rating_id: 'rat-1',
+ rating: 'CONVERSATION_RATING_VALUE_BAD',
+ comment: 'wrong answer',
+ metadata: { channel: 'assistant' },
+ });
+ assert.equal(response.rating.conversationId, 'conv-rating');
+ assert.equal(response.summary.badCount, 1);
+ } finally {
+ await env.close();
+ }
+});
+
+test('conformance shutdown flush semantics', async () => {
+ const env = await createConformanceEnv({ batchSize: 10 });
+
+ try {
+ const recorder = env.client.startGeneration({
+ conversationId: 'conv-shutdown',
+ agentName: 'agent-shutdown',
+ agentVersion: 'v-shutdown',
+ model: { provider: 'openai', name: 'gpt-5' },
+ });
+ recorder.setResult({});
+ recorder.end();
+ assert.equal(recorder.getError(), undefined);
+ assert.equal(env.receivedRequests.length, 0);
+
+ await env.client.shutdown();
+ const generation = env.singleGeneration();
+ assert.equal(generation.conversationId, 'conv-shutdown');
+ assert.equal(generation.agentName, 'agent-shutdown');
+ assert.equal(generation.agentVersion, 'v-shutdown');
+ } finally {
+ await env.close();
+ }
+});
+
+async function createConformanceEnv(options = {}) {
+ const receivedRequests = [];
+ const grpcServer = await startGRPCServer((request) => {
+ receivedRequests.push(request);
+ });
+
+ let ratingPath = '';
+ let ratingPayload = undefined;
+ const ratingServer = createServer(async (request, response) => {
+ ratingPath = request.url ?? '';
+ const chunks = [];
+ for await (const chunk of request) {
+ chunks.push(chunk);
+ }
+ ratingPayload = JSON.parse(Buffer.concat(chunks).toString('utf8'));
+ response.writeHead(200, { 'content-type': 'application/json' });
+ response.end(
+ JSON.stringify({
+ rating: {
+ rating_id: 'rat-1',
+ conversation_id: 'conv-rating',
+ rating: 'CONVERSATION_RATING_VALUE_BAD',
+ created_at: '2026-03-12T09:00:00Z',
+ },
+ summary: {
+ total_count: 1,
+ good_count: 0,
+ bad_count: 1,
+ latest_rating: 'CONVERSATION_RATING_VALUE_BAD',
+ latest_rated_at: '2026-03-12T09:00:00Z',
+ has_bad_rating: true,
+ },
+ })
+ );
+ });
+ await listen(ratingServer);
+ const ratingAddress = ratingServer.address();
+ if (ratingAddress === null || typeof ratingAddress === 'string') {
+ throw new Error('failed to resolve rating server address');
+ }
+
+ const spanExporter = new InMemorySpanExporter();
+ const tracerProvider = new BasicTracerProvider({
+ spanProcessors: [new SimpleSpanProcessor(spanExporter)],
+ });
+ const metricExporter = new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE);
+ const metricReader = new PeriodicExportingMetricReader({
+ exporter: metricExporter,
+ exportIntervalMillis: 60_000,
+ });
+ const meterProvider = new MeterProvider({
+ readers: [metricReader],
+ });
+
+ const defaults = defaultConfig();
+ const client = new SigilClient({
+ tracer: tracerProvider.getTracer('sigil-conformance-test'),
+ meter: meterProvider.getMeter('sigil-conformance-test'),
+ generationExport: {
+ ...defaults.generationExport,
+ protocol: 'grpc',
+ endpoint: `127.0.0.1:${grpcServer.port}`,
+ insecure: true,
+ batchSize: options.batchSize ?? 1,
+ queueSize: 10,
+ flushIntervalMs: 60 * 60 * 1_000,
+ maxRetries: 1,
+ initialBackoffMs: 1,
+ maxBackoffMs: 2,
+ },
+ api: {
+ endpoint: `http://127.0.0.1:${ratingAddress.port}`,
+ },
+ });
+
+ let closed = false;
+ return {
+ client,
+ receivedRequests,
+ get ratingPath() {
+ return ratingPath;
+ },
+ get ratingPayload() {
+ return ratingPayload;
+ },
+ singleGeneration() {
+ assert.equal(receivedRequests.length, 1);
+ assert.equal(receivedRequests[0].generations?.length, 1);
+ return receivedRequests[0].generations[0];
+ },
+ latestGenerationSpan() {
+ const spans = spanExporter.getFinishedSpans().filter((span) => {
+ const operation = span.attributes['gen_ai.operation.name'];
+ return operation === 'generateText' || operation === 'streamText';
+ });
+ assert.ok(spans.length > 0);
+ return spans.at(-1);
+ },
+ latestSpanByOperation(operationName) {
+ const spans = spanExporter
+ .getFinishedSpans()
+ .filter((span) => span.attributes['gen_ai.operation.name'] === operationName);
+ assert.ok(spans.length > 0);
+ return spans.at(-1);
+ },
+ async metricNames() {
+ await meterProvider.forceFlush();
+ return metricExporter
+ .getMetrics()
+ .flatMap((resourceMetrics) => resourceMetrics.scopeMetrics)
+ .flatMap((scopeMetrics) => scopeMetrics.metrics)
+ .map((metric) => metric.descriptor.name);
+ },
+ async close() {
+ if (closed) {
+ return;
+ }
+ closed = true;
+ await client.shutdown();
+ await meterProvider.shutdown();
+ await tracerProvider.shutdown();
+ await close(ratingServer);
+ await stopGRPCServer(grpcServer.server);
+ },
+ };
+}
+
+async function runWithMaybeContext(value, wrapper, callback) {
+ if (typeof value === 'string' && value.trim().length > 0) {
+ return await wrapper(value, callback);
+ }
+ return await callback();
+}
+
+function listen(server) {
+ return new Promise((resolve, reject) => {
+ server.once('error', reject);
+ server.listen(0, '127.0.0.1', () => {
+ server.off('error', reject);
+ resolve();
+ });
+ });
+}
+
+function close(server) {
+ return new Promise((resolve, reject) => {
+ server.close((error) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve();
+ });
+ });
+}
+
+async function startGRPCServer(onRequest) {
+ const packageDefinition = await protoLoader.load(protoPath, protoLoadOptions);
+ const loaded = grpc.loadPackageDefinition(packageDefinition);
+ const service = loaded.sigil.v1.GenerationIngestService;
+
+ const server = new grpc.Server();
+ server.addService(service.service, {
+ ExportGenerations(call, callback) {
+ onRequest(call.request, call.metadata.getMap());
+ callback(null, {
+ results: (call.request.generations ?? []).map((generation) => ({
+ generationId: generation.id,
+ accepted: true,
+ })),
+ });
+ },
+ });
+
+ const port = await new Promise((resolve, reject) => {
+ server.bindAsync('127.0.0.1:0', grpc.ServerCredentials.createInsecure(), (error, boundPort) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+ resolve(boundPort);
+ });
+ });
+
+ server.start();
+ return { server, port };
+}
+
+function stopGRPCServer(server) {
+ return new Promise((resolve) => {
+ server.tryShutdown(() => {
+ resolve();
+ });
+ });
+}
diff --git a/js/test/frameworks.additional.test.mjs b/js/test/frameworks.additional.test.mjs
index e84bccd..8c81fd2 100644
--- a/js/test/frameworks.additional.test.mjs
+++ b/js/test/frameworks.additional.test.mjs
@@ -1,5 +1,7 @@
import assert from 'node:assert/strict';
import test from 'node:test';
+import { context, trace } from '@opentelemetry/api';
+import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { defaultConfig, SigilClient } from '../.test-dist/index.js';
import {
@@ -254,6 +256,87 @@ for (const framework of frameworks) {
assert.deepEqual(generation.metadata.first, { nested: { ok: true } });
assert.deepEqual(generation.metadata.second, { nested: { ok: true } });
});
+
+ test(`${framework.name} generation span tracks active parent span and preserves export lineage`, async () => {
+ const spanExporter = new InMemorySpanExporter();
+ const tracerProvider = new BasicTracerProvider({
+ spanProcessors: [new SimpleSpanProcessor(spanExporter)],
+ });
+ const baseTracer = tracerProvider.getTracer('sigil-framework-test');
+ let parentContext;
+ const tracer = {
+ startSpan(name, options, contextArg) {
+ return baseTracer.startSpan(name, options, contextArg ?? parentContext);
+ },
+ startActiveSpan(...args) {
+ return baseTracer.startActiveSpan(...args);
+ },
+ };
+ const defaults = defaultConfig();
+ const exporter = new CapturingExporter();
+ const client = new SigilClient({
+ generationExport: {
+ ...defaults.generationExport,
+ batchSize: 10,
+ flushIntervalMs: 60_000,
+ },
+ generationExporter: exporter,
+ tracer,
+ });
+
+ try {
+ const handler = new framework.handlerCtor(client);
+ const parentSpan = baseTracer.startSpan('framework.request');
+ parentContext = trace.setSpan(context.active(), parentSpan);
+ await handler.handleChatModelStart(
+ { name: 'ChatModel' },
+ [[{ type: 'human', content: 'hello' }]],
+ 'run-lineage',
+ 'parent-run-lineage',
+ { invocation_params: { model: 'gpt-5' } },
+ ['prod'],
+ {
+ conversation_id: 'framework-conversation-lineage-42',
+ thread_id: 'framework-thread-lineage-42',
+ }
+ );
+ await handler.handleLLMEnd(
+ {
+ generations: [[{ text: 'world' }]],
+ llm_output: { model_name: 'gpt-5', finish_reason: 'stop' },
+ },
+ 'run-lineage'
+ );
+ parentSpan.end();
+
+ await client.flush();
+ const generation = exporter.requests[0].generations[0];
+ const generationSpan = spanExporter
+ .getFinishedSpans()
+ .find((span) => span.attributes['gen_ai.operation.name'] === 'generateText');
+
+ assert.ok(generationSpan);
+ assert.equal(generationSpan.parentSpanContext?.spanId, parentSpan.spanContext().spanId);
+ assert.equal(generationSpan.spanContext().traceId, parentSpan.spanContext().traceId);
+ assert.equal(generation.traceId, generationSpan.spanContext().traceId);
+ assert.equal(generation.spanId, generationSpan.spanContext().spanId);
+ } finally {
+ await client.shutdown();
+ await tracerProvider.shutdown();
+ }
+ });
+
+ test(`${framework.name} handler explicitly has no embedding lifecycle`, async () => {
+ const client = new SigilClient(defaultConfig());
+ try {
+ const handler = new framework.handlerCtor(client);
+ assert.equal(typeof handler.handleEmbeddingStart, 'undefined');
+ assert.equal(typeof handler.handleEmbeddingEnd, 'undefined');
+ assert.equal(typeof handler.handleEmbeddingError, 'undefined');
+ } finally {
+ await client.shutdown();
+ }
+ });
}
async function captureSingleGeneration(run) {
diff --git a/js/test/frameworks.langchain.test.mjs b/js/test/frameworks.langchain.test.mjs
index f4bf9e0..c081c29 100644
--- a/js/test/frameworks.langchain.test.mjs
+++ b/js/test/frameworks.langchain.test.mjs
@@ -1,6 +1,6 @@
import assert from 'node:assert/strict';
import test from 'node:test';
-import { SpanStatusCode } from '@opentelemetry/api';
+import { context, SpanStatusCode, trace } from '@opentelemetry/api';
import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { defaultConfig, SigilClient } from '../.test-dist/index.js';
import { SigilLangChainHandler } from '../.test-dist/frameworks/langchain/index.js';
@@ -143,6 +143,72 @@ test('langchain handler records first token timestamp once per run', async () =>
}
});
+test('langchain generation span tracks active parent span and preserves export lineage', async () => {
+ const spanExporter = new InMemorySpanExporter();
+ const tracerProvider = new BasicTracerProvider({
+ spanProcessors: [new SimpleSpanProcessor(spanExporter)],
+ });
+ const baseTracer = tracerProvider.getTracer('sigil-framework-test');
+ let parentContext;
+ const tracer = {
+ startSpan(name, options, contextArg) {
+ return baseTracer.startSpan(name, options, contextArg ?? parentContext);
+ },
+ startActiveSpan(...args) {
+ return baseTracer.startActiveSpan(...args);
+ },
+ };
+ const defaults = defaultConfig();
+ const exporter = new CapturingExporter();
+ const client = new SigilClient({
+ generationExport: {
+ ...defaults.generationExport,
+ batchSize: 10,
+ flushIntervalMs: 60_000,
+ },
+ generationExporter: exporter,
+ tracer,
+ });
+
+ try {
+ const handler = new SigilLangChainHandler(client);
+ const parentSpan = baseTracer.startSpan('framework.request');
+ parentContext = trace.setSpan(context.active(), parentSpan);
+ await handler.handleChatModelStart(
+ { name: 'ChatOpenAI' },
+ [[{ type: 'human', content: 'hello' }]],
+ 'run-lineage',
+ 'parent-run-lineage',
+ { invocation_params: { model: 'gpt-5' } },
+ ['prod'],
+ { thread_id: 'chain-thread-lineage-42' }
+ );
+ await handler.handleLLMEnd(
+ {
+ generations: [[{ text: 'world' }]],
+ llm_output: { model_name: 'gpt-5', finish_reason: 'stop' },
+ },
+ 'run-lineage'
+ );
+ parentSpan.end();
+
+ await client.flush();
+ const generation = exporter.requests[0].generations[0];
+ const generationSpan = spanExporter
+ .getFinishedSpans()
+ .find((span) => span.attributes['gen_ai.operation.name'] === 'generateText');
+
+ assert.ok(generationSpan);
+ assert.equal(generationSpan.parentSpanContext?.spanId, parentSpan.spanContext().spanId);
+ assert.equal(generationSpan.spanContext().traceId, parentSpan.spanContext().traceId);
+ assert.equal(generation.traceId, generationSpan.spanContext().traceId);
+ assert.equal(generation.spanId, generationSpan.spanContext().spanId);
+ } finally {
+ await client.shutdown();
+ await tracerProvider.shutdown();
+ }
+});
+
test('langchain provider mapping covers openai anthopic gemini and fallback', async () => {
const providers = [];
@@ -177,6 +243,18 @@ test('langchain handler sets call_error on llm error', async () => {
assert.equal(generation.tags['sigil.framework.name'], 'langchain');
});
+test('langchain handler explicitly has no embedding lifecycle', async () => {
+ const client = new SigilClient(defaultConfig());
+ try {
+ const handler = new SigilLangChainHandler(client);
+ assert.equal(typeof handler.handleEmbeddingStart, 'undefined');
+ assert.equal(typeof handler.handleEmbeddingEnd, 'undefined');
+ assert.equal(typeof handler.handleEmbeddingError, 'undefined');
+ } finally {
+ await client.shutdown();
+ }
+});
+
test('langchain handler maps tool callbacks and emits chain/retriever spans', async () => {
const spanExporter = new InMemorySpanExporter();
const tracerProvider = new BasicTracerProvider({
diff --git a/js/test/frameworks.langgraph.test.mjs b/js/test/frameworks.langgraph.test.mjs
index 25c7807..d3e541f 100644
--- a/js/test/frameworks.langgraph.test.mjs
+++ b/js/test/frameworks.langgraph.test.mjs
@@ -1,6 +1,6 @@
import assert from 'node:assert/strict';
import test from 'node:test';
-import { SpanStatusCode } from '@opentelemetry/api';
+import { context, SpanStatusCode, trace } from '@opentelemetry/api';
import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { defaultConfig, SigilClient } from '../.test-dist/index.js';
import { SigilLangGraphHandler } from '../.test-dist/frameworks/langgraph/index.js';
@@ -97,6 +97,72 @@ test('langgraph handler records stream mode and token fallback output', async ()
assert.equal(generation.output[0].content, 'hello world');
});
+test('langgraph generation span tracks active parent span and preserves export lineage', async () => {
+ const spanExporter = new InMemorySpanExporter();
+ const tracerProvider = new BasicTracerProvider({
+ spanProcessors: [new SimpleSpanProcessor(spanExporter)],
+ });
+ const baseTracer = tracerProvider.getTracer('sigil-framework-test');
+ let parentContext;
+ const tracer = {
+ startSpan(name, options, contextArg) {
+ return baseTracer.startSpan(name, options, contextArg ?? parentContext);
+ },
+ startActiveSpan(...args) {
+ return baseTracer.startActiveSpan(...args);
+ },
+ };
+ const defaults = defaultConfig();
+ const exporter = new CapturingExporter();
+ const client = new SigilClient({
+ generationExport: {
+ ...defaults.generationExport,
+ batchSize: 10,
+ flushIntervalMs: 60_000,
+ },
+ generationExporter: exporter,
+ tracer,
+ });
+
+ try {
+ const handler = new SigilLangGraphHandler(client);
+ const parentSpan = baseTracer.startSpan('framework.request');
+ parentContext = trace.setSpan(context.active(), parentSpan);
+ await handler.handleChatModelStart(
+ { name: 'ChatOpenAI' },
+ [[{ type: 'human', content: 'hello' }]],
+ 'run-lineage',
+ 'parent-run-lineage',
+ { invocation_params: { model: 'gpt-5' } },
+ ['prod'],
+ { thread_id: 'graph-thread-lineage-42', langgraph_node: 'answer_node' }
+ );
+ await handler.handleLLMEnd(
+ {
+ generations: [[{ text: 'world' }]],
+ llm_output: { model_name: 'gpt-5', finish_reason: 'stop' },
+ },
+ 'run-lineage'
+ );
+ parentSpan.end();
+
+ await client.flush();
+ const generation = exporter.requests[0].generations[0];
+ const generationSpan = spanExporter
+ .getFinishedSpans()
+ .find((span) => span.attributes['gen_ai.operation.name'] === 'generateText');
+
+ assert.ok(generationSpan);
+ assert.equal(generationSpan.parentSpanContext?.spanId, parentSpan.spanContext().spanId);
+ assert.equal(generationSpan.spanContext().traceId, parentSpan.spanContext().traceId);
+ assert.equal(generation.traceId, generationSpan.spanContext().traceId);
+ assert.equal(generation.spanId, generationSpan.spanContext().spanId);
+ } finally {
+ await client.shutdown();
+ await tracerProvider.shutdown();
+ }
+});
+
test('langgraph provider mapping covers openai anthopic gemini and fallback', async () => {
const providers = [];
@@ -131,6 +197,18 @@ test('langgraph handler sets call_error on llm error', async () => {
assert.equal(generation.tags['sigil.framework.name'], 'langgraph');
});
+test('langgraph handler explicitly has no embedding lifecycle', async () => {
+ const client = new SigilClient(defaultConfig());
+ try {
+ const handler = new SigilLangGraphHandler(client);
+ assert.equal(typeof handler.handleEmbeddingStart, 'undefined');
+ assert.equal(typeof handler.handleEmbeddingEnd, 'undefined');
+ assert.equal(typeof handler.handleEmbeddingError, 'undefined');
+ } finally {
+ await client.shutdown();
+ }
+});
+
test('langgraph handler maps tool callbacks and emits chain/retriever spans', async () => {
const spanExporter = new InMemorySpanExporter();
const tracerProvider = new BasicTracerProvider({
diff --git a/js/test/providers.test.mjs b/js/test/providers.test.mjs
index c2c1687..b77ccbc 100644
--- a/js/test/providers.test.mjs
+++ b/js/test/providers.test.mjs
@@ -549,6 +549,172 @@ test('embedding provider wrapper errors set provider_call_error span status', as
}
});
+test('provider mappers throw on missing provider responses and stream summaries', () => {
+ assert.throws(
+ () => openai.chat.completions.fromRequestResponse(
+ {
+ model: 'gpt-5',
+ messages: [{ role: 'user', content: 'hello' }],
+ },
+ undefined
+ ),
+ /reading 'id'/
+ );
+ assert.throws(
+ () => openai.responses.fromRequestResponse(
+ {
+ model: 'gpt-5',
+ input: 'hello',
+ },
+ undefined
+ ),
+ /reading 'id'/
+ );
+ assert.throws(
+ () => openai.chat.completions.fromStream(
+ {
+ model: 'gpt-5',
+ stream: true,
+ messages: [{ role: 'user', content: 'hello' }],
+ },
+ undefined
+ ),
+ /reading 'outputText'/
+ );
+ assert.throws(
+ () => openai.responses.fromStream(
+ {
+ model: 'gpt-5',
+ stream: true,
+ input: 'hello',
+ },
+ undefined
+ ),
+ /reading 'events'/
+ );
+
+ assert.throws(
+ () => anthropic.messages.fromRequestResponse(
+ {
+ model: 'claude-sonnet-4-5',
+ max_tokens: 128,
+ messages: [{ role: 'user', content: [{ type: 'text', text: 'hello' }] }],
+ },
+ undefined
+ ),
+ /reading 'content'/
+ );
+ assert.throws(
+ () => anthropic.messages.fromStream(
+ {
+ model: 'claude-sonnet-4-5',
+ max_tokens: 128,
+ messages: [{ role: 'user', content: [{ type: 'text', text: 'hello' }] }],
+ },
+ undefined
+ ),
+ /reading 'events'/
+ );
+
+ assert.throws(
+ () => gemini.models.fromRequestResponse(
+ 'gemini-2.5-pro',
+ [{ role: 'user', parts: [{ text: 'hello' }] }],
+ undefined,
+ undefined
+ ),
+ /reading 'candidates'/
+ );
+ assert.throws(
+ () => gemini.models.fromStream(
+ 'gemini-2.5-pro',
+ [{ role: 'user', parts: [{ text: 'hello' }] }],
+ undefined,
+ undefined
+ ),
+ /reading 'responses'/
+ );
+});
+
+test('provider wrappers surface mapper failures when provider payloads are missing', async () => {
+ for (const suite of [
+ {
+ provider: 'openai',
+ error: /reading 'id'/,
+ run: async (client) => {
+ await openai.chat.completions.create(
+ client,
+ {
+ model: 'gpt-5',
+ messages: [{ role: 'user', content: 'hello' }],
+ },
+ async () => undefined
+ );
+ },
+ },
+ {
+ provider: 'openai',
+ error: /reading 'id'/,
+ run: async (client) => {
+ await openai.responses.create(
+ client,
+ {
+ model: 'gpt-5',
+ input: 'hello',
+ },
+ async () => undefined
+ );
+ },
+ },
+ {
+ provider: 'anthropic',
+ error: /reading 'content'/,
+ run: async (client) => {
+ await anthropic.messages.create(
+ client,
+ {
+ model: 'claude-sonnet-4-5',
+ max_tokens: 128,
+ messages: [{ role: 'user', content: [{ type: 'text', text: 'hello' }] }],
+ },
+ async () => undefined
+ );
+ },
+ },
+ {
+ provider: 'gemini',
+ error: /reading 'candidates'/,
+ run: async (client) => {
+ await gemini.models.generateContent(
+ client,
+ 'gemini-2.5-pro',
+ [{ role: 'user', parts: [{ text: 'hello' }] }],
+ undefined,
+ async () => undefined
+ );
+ },
+ },
+ ]) {
+ const exporter = new CapturingExporter();
+ const client = newClient(exporter);
+ try {
+ await assert.rejects(suite.run(client), suite.error);
+ await client.flush();
+ const generation = firstGeneration(exporter);
+ assert.equal(generation.model.provider, suite.provider);
+ assert.match(generation.callError ?? '', suite.error);
+ assert.equal(generation.output, undefined);
+ } finally {
+ await client.shutdown();
+ }
+ }
+});
+
+test('anthropic provider namespace explicitly has no embeddings surface', () => {
+ assert.ok(anthropic.messages);
+ assert.equal(anthropic.embeddings, undefined);
+});
+
test('provider wrappers propagate provider errors and persist callError', async () => {
for (const suite of [
{
@@ -653,7 +819,7 @@ test('openai chat mapper aggregates system/developer, preserves tool role, and a
{ role: 'system', content: 'system-message' },
{ role: 'developer', content: 'developer-message' },
{ role: 'user', content: 'hello' },
- { role: 'tool', content: '{"ok":true}', name: 'tool-weather' },
+ { role: 'tool', tool_call_id: 'call_weather', content: '{"ok":true}', name: 'tool-weather' },
],
tools: [
{
@@ -704,6 +870,10 @@ test('openai chat mapper aggregates system/developer, preserves tool role, and a
assert.equal(mappedDefault.input.length, 2);
assert.equal(mappedDefault.input[0].role, 'user');
assert.equal(mappedDefault.input[1].role, 'tool');
+ assert.equal(mappedDefault.input[1].parts[0].type, 'tool_result');
+ assert.equal(mappedDefault.input[1].parts[0].toolResult.toolCallId, 'call_weather');
+ assert.equal(mappedDefault.input[1].parts[0].toolResult.name, 'tool-weather');
+ assert.equal(mappedDefault.input[1].parts[0].toolResult.content, '{"ok":true}');
assert.equal(mappedDefault.maxTokens, 256);
assert.equal(mappedDefault.temperature, 0.3);
assert.equal(mappedDefault.topP, 0.8);
@@ -731,6 +901,12 @@ test('openai responses mapper maps input/output/usage and stream fallback from e
role: 'user',
content: [{ type: 'input_text', text: 'hello' }],
},
+ {
+ type: 'function_call_output',
+ call_id: 'call_weather',
+ name: 'weather',
+ output: { temp_c: 18 },
+ },
],
max_output_tokens: 300,
tool_choice: { type: 'function', name: 'weather' },
@@ -756,6 +932,13 @@ test('openai responses mapper maps input/output/usage and stream fallback from e
name: 'weather',
arguments: '{"city":"Paris"}',
},
+ {
+ id: 'result-1',
+ type: 'function_call_output',
+ call_id: 'call_weather',
+ name: 'weather',
+ output: { temp_c: 18 },
+ },
],
status: 'completed',
parallel_tool_calls: false,
@@ -777,15 +960,23 @@ test('openai responses mapper maps input/output/usage and stream fallback from e
const mapped = openai.responses.fromRequestResponse(request, response);
assert.equal(mapped.responseModel, 'gpt-5');
- assert.equal(mapped.input.length, 1);
+ assert.equal(mapped.input.length, 2);
assert.equal(mapped.input[0].role, 'user');
assert.equal(mapped.input[0].content, 'hello');
+ assert.equal(mapped.input[1].role, 'tool');
+ assert.equal(mapped.input[1].parts[0].type, 'tool_result');
+ assert.equal(mapped.input[1].parts[0].toolResult.toolCallId, 'call_weather');
+ assert.equal(mapped.input[1].parts[0].toolResult.contentJSON, '{"temp_c":18}');
assert.equal(mapped.maxTokens, 300);
assert.equal(mapped.stopReason, 'stop');
assert.equal(mapped.thinkingEnabled, true);
assert.equal(mapped.metadata['sigil.gen_ai.request.thinking.budget_tokens'], 640);
assert.equal(mapped.usage.totalTokens, 100);
- assert.equal(mapped.output.length > 0, true);
+ assert.equal(mapped.output.length, 3);
+ assert.equal(mapped.output[2].role, 'tool');
+ assert.equal(mapped.output[2].parts[0].type, 'tool_result');
+ assert.equal(mapped.output[2].parts[0].toolResult.toolCallId, 'call_weather');
+ assert.equal(mapped.output[2].parts[0].toolResult.contentJSON, '{"temp_c":18}');
const streamed = openai.responses.fromStream(
{ ...request, stream: true },
@@ -813,7 +1004,7 @@ test('openai responses mapper maps input/output/usage and stream fallback from e
);
assert.equal(streamed.responseModel, 'gpt-5');
- assert.equal(streamed.input.length, 1);
+ assert.equal(streamed.input.length, 2);
assert.equal(streamed.input[0].content, 'hello');
assert.equal(streamed.output.length, 1);
assert.equal(streamed.output[0].content, 'delta-one delta-two');
diff --git a/js/tsconfig.json b/js/tsconfig.json
index 05f6fb8..cfb0672 100644
--- a/js/tsconfig.json
+++ b/js/tsconfig.json
@@ -7,7 +7,8 @@
"noImplicitOverride": true,
"noUncheckedIndexedAccess": true,
"skipLibCheck": true,
- "noEmit": true
+ "noEmit": true,
+ "types": ["node"]
},
"include": ["src/**/*.ts"]
}
diff --git a/js/tsconfig.test.json b/js/tsconfig.test.json
index f4844f7..096bea5 100644
--- a/js/tsconfig.test.json
+++ b/js/tsconfig.test.json
@@ -4,7 +4,8 @@
"module": "NodeNext",
"moduleResolution": "NodeNext",
"noEmit": false,
- "outDir": ".test-dist"
+ "outDir": ".test-dist",
+ "rootDir": "./src"
},
"include": ["src/**/*.ts"]
}
diff --git a/mise.toml b/mise.toml
new file mode 100644
index 0000000..96e0dd6
--- /dev/null
+++ b/mise.toml
@@ -0,0 +1,428 @@
+[env]
+PYTHON_BIN = "python3"
+
+# --- Dependencies ---
+
+[tasks.deps]
+description = "Install JS/TS dependencies"
+run = "pnpm install"
+
+# --- Formatting ---
+
+[tasks."format:go"]
+description = "Format Go code in all modules"
+run = """
+#!/usr/bin/env bash
+set -euo pipefail
+while IFS= read -r moddir; do
+ echo "==> gofmt ${moddir}"
+ gofmt -w "${moddir}"
+done < <(find . -name go.mod -not -path './node_modules/*' -exec dirname {} \\; | sort)
+"""
+
+[tasks."format:cs"]
+description = "Format .NET SDK code"
+run = "dotnet format dotnet/Sigil.DotNet.sln"
+
+[tasks.format]
+description = "Format all code"
+depends = ["format:go", "format:cs"]
+
+# --- Linting ---
+
+[tasks."lint:go"]
+description = "Lint Go code in all modules"
+run = """
+#!/usr/bin/env bash
+set -euo pipefail
+while IFS= read -r moddir; do
+ if [[ -z "$(cd "${moddir}" && GOWORK=off go list ./... 2>/dev/null || true)" ]]; then
+ echo "==> skip golangci-lint ${moddir} (no Go packages)"
+ continue
+ fi
+ echo "==> golangci-lint ${moddir}"
+ (cd "${moddir}" && GOWORK=off golangci-lint run ./...)
+done < <(find . -name go.mod -not -path './node_modules/*' -exec dirname {} \\; | sort)
+"""
+
+[tasks."lint:cs"]
+description = "Verify .NET SDK formatting and analyzer checks"
+run = "dotnet format dotnet/Sigil.DotNet.sln --verify-no-changes"
+
+[tasks.lint]
+description = "Run all linting"
+depends = ["lint:go", "lint:cs"]
+
+# --- Type checking ---
+
+[tasks."typecheck:ts:sdk-js"]
+description = "Type-check TypeScript/JavaScript SDK code"
+depends = ["deps"]
+run = "pnpm --filter @grafana/sigil-sdk-js run typecheck"
+
+[tasks.typecheck]
+description = "Run all type checks"
+depends = ["typecheck:ts:sdk-js"]
+
+# --- Go SDK tests ---
+
+[tasks."test:go:sdk-core"]
+description = "Run Go SDK core tests as standalone module"
+dir = "go"
+run = "GOWORK=off go test ./..."
+
+[tasks."test:go:sdk-anthropic"]
+description = "Run Anthropic provider helper tests as standalone module"
+dir = "go-providers/anthropic"
+run = "GOWORK=off go test ./..."
+
+[tasks."test:go:sdk-openai"]
+description = "Run OpenAI provider helper tests as standalone module"
+dir = "go-providers/openai"
+run = "GOWORK=off go test ./..."
+
+[tasks."test:go:sdk-gemini"]
+description = "Run Gemini provider helper tests as standalone module"
+dir = "go-providers/gemini"
+run = "GOWORK=off go test ./..."
+
+[tasks."test:go:sdk-google-adk"]
+description = "Run Google ADK framework helper tests as standalone module"
+dir = "go-frameworks/google-adk"
+run = "GOWORK=off go test ./..."
+
+[tasks."test:go:sdk-conformance"]
+description = "Run the Go SDK core conformance harness"
+dir = "go"
+run = "GOWORK=off go test ./sigil -run '^TestConformance' -count=1"
+
+# --- TypeScript/JavaScript SDK tests ---
+
+[tasks."test:ts:sdk-js"]
+description = "Run TypeScript/JavaScript SDK runtime tests"
+depends = ["deps"]
+run = "pnpm --filter @grafana/sigil-sdk-js run test:ci"
+
+[tasks."test:ts:sdk-conformance"]
+description = "Run TypeScript/JavaScript SDK core conformance tests"
+depends = ["deps"]
+dir = "js"
+run = "pnpm run test:build && node --test test/conformance.test.mjs"
+
+[tasks."test:ts:sdk-provider-conformance"]
+description = "Run TypeScript/JavaScript provider-wrapper conformance tests"
+depends = ["deps"]
+dir = "js"
+run = "pnpm run test:build && node --test test/providers.test.mjs"
+
+[tasks."test:ts:sdk-framework-conformance"]
+description = "Run TypeScript/JavaScript framework-adapter conformance tests"
+depends = ["deps"]
+run = "mise run test:ts:sdk-js-frameworks"
+
+[tasks."test:ts:sdk-js-frameworks"]
+description = "Run TypeScript/JavaScript SDK framework handler tests"
+depends = ["deps"]
+dir = "js"
+run = "pnpm run test:build && node --test test/frameworks.langchain.test.mjs test/frameworks.langgraph.test.mjs test/frameworks.additional.test.mjs test/frameworks.vercel-ai-sdk.mapping.test.mjs test/frameworks.vercel-ai-sdk.test.mjs"
+
+# --- Python SDK tests ---
+
+[tasks."test:py:sdk-core"]
+description = "Run Python SDK core parity tests"
+run = "uv run --python \"$PYTHON_BIN\" --with '.[dev]' --directory python pytest tests"
+
+[tasks."test:py:sdk-conformance"]
+description = "Run Python SDK core conformance tests"
+run = "uv run --python \"$PYTHON_BIN\" --with '.[dev]' --directory python pytest tests/test_conformance.py"
+
+[tasks."test:py:sdk-openai"]
+description = "Run Python OpenAI provider helper tests"
+run = "uv run --python \"$PYTHON_BIN\" --with './python[dev]' --with './python-providers/openai[dev]' pytest python-providers/openai/tests"
+
+[tasks."test:py:sdk-anthropic"]
+description = "Run Python Anthropic provider helper tests"
+run = "uv run --python \"$PYTHON_BIN\" --with './python[dev]' --with './python-providers/anthropic[dev]' pytest python-providers/anthropic/tests"
+
+[tasks."test:py:sdk-gemini"]
+description = "Run Python Gemini provider helper tests"
+run = "uv run --python \"$PYTHON_BIN\" --with './python[dev]' --with './python-providers/gemini[dev]' pytest python-providers/gemini/tests"
+
+[tasks."test:py:sdk-langchain"]
+description = "Run Python LangChain framework handler tests"
+run = "uv run --python \"$PYTHON_BIN\" --with './python[dev]' --with './python-frameworks/langchain[dev]' pytest python-frameworks/langchain/tests"
+
+[tasks."test:py:sdk-langgraph"]
+description = "Run Python LangGraph framework handler tests"
+run = "uv run --python \"$PYTHON_BIN\" --with './python[dev]' --with './python-frameworks/langgraph[dev]' pytest python-frameworks/langgraph/tests"
+
+[tasks."test:py:sdk-openai-agents"]
+description = "Run Python OpenAI Agents framework handler tests"
+run = "uv run --python \"$PYTHON_BIN\" --with './python[dev]' --with './python-frameworks/openai-agents[dev]' pytest python-frameworks/openai-agents/tests"
+
+[tasks."test:py:sdk-llamaindex"]
+description = "Run Python LlamaIndex framework handler tests"
+run = "uv run --python \"$PYTHON_BIN\" --with './python[dev]' --with './python-frameworks/llamaindex[dev]' pytest python-frameworks/llamaindex/tests"
+
+[tasks."test:py:sdk-google-adk"]
+description = "Run Python Google ADK framework handler tests"
+run = "uv run --python \"$PYTHON_BIN\" --with './python[dev]' --with './python-frameworks/google-adk[dev]' pytest python-frameworks/google-adk/tests"
+
+[tasks."test:py:sdk-provider-conformance"]
+description = "Run Python provider-wrapper conformance tests"
+run = """
+#!/usr/bin/env bash
+set -euo pipefail
+mise run test:py:sdk-openai
+mise run test:py:sdk-anthropic
+mise run test:py:sdk-gemini
+"""
+
+[tasks."test:py:sdk-framework-conformance"]
+description = "Run Python framework-adapter conformance tests"
+run = """
+#!/usr/bin/env bash
+set -euo pipefail
+mise run test:py:sdk-langchain
+mise run test:py:sdk-langgraph
+mise run test:py:sdk-openai-agents
+mise run test:py:sdk-llamaindex
+mise run test:py:sdk-google-adk
+"""
+
+# --- .NET SDK tests ---
+
+[tasks."test:cs:sdk-core"]
+description = "Run .NET SDK core runtime tests"
+dir = "dotnet"
+run = "dotnet test tests/Grafana.Sigil.Tests/Grafana.Sigil.Tests.csproj -c Release"
+
+[tasks."test:cs:sdk-conformance"]
+description = "Run .NET SDK core conformance tests"
+dir = "dotnet"
+run = "dotnet test tests/Grafana.Sigil.Tests/Grafana.Sigil.Tests.csproj -c Release --filter FullyQualifiedName~ConformanceTests"
+
+[tasks."test:cs:sdk-openai"]
+description = "Run .NET OpenAI provider helper tests"
+dir = "dotnet"
+run = "dotnet test tests/Grafana.Sigil.OpenAI.Tests/Grafana.Sigil.OpenAI.Tests.csproj -c Release"
+
+[tasks."test:cs:sdk-anthropic"]
+description = "Run .NET Anthropic provider helper tests"
+dir = "dotnet"
+run = "dotnet test tests/Grafana.Sigil.Anthropic.Tests/Grafana.Sigil.Anthropic.Tests.csproj -c Release"
+
+[tasks."test:cs:sdk-gemini"]
+description = "Run .NET Gemini provider helper tests"
+dir = "dotnet"
+run = "dotnet test tests/Grafana.Sigil.Gemini.Tests/Grafana.Sigil.Gemini.Tests.csproj -c Release"
+
+[tasks."test:cs:sdk-provider-conformance"]
+description = "Run .NET provider-wrapper conformance tests"
+run = """
+#!/usr/bin/env bash
+set -euo pipefail
+mise run test:cs:sdk-openai
+mise run test:cs:sdk-anthropic
+mise run test:cs:sdk-gemini
+"""
+
+# --- Java SDK tests ---
+
+[tasks."test:java:sdk-core"]
+description = "Run Java SDK core tests"
+dir = "java"
+run = "./gradlew --no-daemon :core:test"
+
+[tasks."test:java:sdk-conformance"]
+description = "Run Java SDK core conformance tests"
+dir = "java"
+run = "./gradlew --no-daemon :core:test --tests 'com.grafana.sigil.sdk.ConformanceTest'"
+
+[tasks."test:java:sdk-openai"]
+description = "Run Java OpenAI provider adapter tests"
+dir = "java"
+run = "./gradlew --no-daemon :providers:openai:test"
+
+[tasks."test:java:sdk-anthropic"]
+description = "Run Java Anthropic provider adapter tests"
+dir = "java"
+run = "./gradlew --no-daemon :providers:anthropic:test"
+
+[tasks."test:java:sdk-gemini"]
+description = "Run Java Gemini provider adapter tests"
+dir = "java"
+run = "./gradlew --no-daemon :providers:gemini:test"
+
+[tasks."test:java:sdk-google-adk"]
+description = "Run Java Google ADK framework adapter tests"
+dir = "java"
+run = "./gradlew --no-daemon :frameworks:google-adk:test"
+
+[tasks."test:java:sdk-all"]
+description = "Run all Java SDK tests"
+dir = "java"
+run = "./gradlew --no-daemon :core:test :providers:openai:test :providers:anthropic:test :providers:gemini:test :frameworks:google-adk:test"
+
+[tasks."test:java:sdk-provider-conformance"]
+description = "Run Java provider-wrapper conformance tests"
+dir = "java"
+run = "./gradlew --no-daemon :providers:openai:test :providers:anthropic:test :providers:gemini:test"
+
+[tasks."test:java:sdk-framework-conformance"]
+description = "Run Java framework-adapter conformance tests"
+dir = "java"
+run = "./gradlew --no-daemon :frameworks:google-adk:test"
+
+[tasks."benchmark:java:sdk"]
+description = "Run Java SDK JMH benchmarks"
+dir = "java"
+run = "./gradlew --no-daemon :benchmarks:jmh"
+
+# --- Cross-SDK conformance ---
+
+[tasks."test:sdk:core-conformance"]
+description = "Run core conformance suites across Go, TypeScript/JavaScript, Python, Java, and .NET SDKs"
+run = """
+#!/usr/bin/env bash
+set -euo pipefail
+mise run test:go:sdk-conformance
+mise run test:ts:sdk-conformance
+mise run test:py:sdk-conformance
+mise run test:java:sdk-conformance
+mise run test:cs:sdk-conformance
+"""
+
+[tasks."test:sdk:provider-conformance"]
+description = "Run provider-wrapper conformance suites across all shipped SDKs"
+run = """
+#!/usr/bin/env bash
+set -euo pipefail
+mise run test:go:sdk-anthropic
+mise run test:go:sdk-openai
+mise run test:go:sdk-gemini
+mise run test:ts:sdk-provider-conformance
+mise run test:py:sdk-provider-conformance
+mise run test:cs:sdk-provider-conformance
+mise run test:java:sdk-provider-conformance
+"""
+
+[tasks."test:sdk:framework-conformance"]
+description = "Run framework-adapter conformance suites across all shipped SDKs"
+run = """
+#!/usr/bin/env bash
+set -euo pipefail
+mise run test:go:sdk-google-adk
+mise run test:ts:sdk-framework-conformance
+mise run test:py:sdk-framework-conformance
+mise run test:java:sdk-framework-conformance
+"""
+
+[tasks."test:sdk:conformance"]
+description = "Run core, provider-wrapper, and framework-adapter conformance suites across supported SDKs"
+run = """
+#!/usr/bin/env bash
+set -euo pipefail
+mise run test:sdk:core-conformance
+mise run test:sdk:provider-conformance
+mise run test:sdk:framework-conformance
+"""
+
+[tasks."test:sdk:all"]
+description = "Run all SDK checks (Go, TypeScript/JavaScript, Python, Java, and .NET SDK suites)"
+run = """
+#!/usr/bin/env bash
+set -euo pipefail
+mise run test:go:sdk-core
+mise run test:go:sdk-anthropic
+mise run test:go:sdk-openai
+mise run test:go:sdk-gemini
+mise run test:go:sdk-google-adk
+mise run test:ts:sdk-js
+mise run test:py:sdk-core
+mise run test:py:sdk-provider-conformance
+mise run test:py:sdk-framework-conformance
+mise run test:cs:sdk-core
+mise run test:cs:sdk-provider-conformance
+mise run test:java:sdk-all
+"""
+
+[tasks."sdk:conformance"]
+description = "Alias for test:sdk:conformance"
+run = "mise run test:sdk:conformance"
+
+# --- Python version bump ---
+
+[tasks."sdk:py:bump"]
+description = "Bump version across all 9 Python SDK pyproject.toml files"
+run = '''
+#!/usr/bin/env bash
+set -euo pipefail
+
+VERSION="${1:?Usage: mise run sdk:py:bump }"
+if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+ echo "Error: version must be in MAJOR.MINOR.PATCH format (got: $VERSION)" >&2
+ exit 1
+fi
+
+PACKAGE_DIRS=(
+ python
+ python-providers/openai
+ python-providers/anthropic
+ python-providers/gemini
+ python-frameworks/langchain
+ python-frameworks/langgraph
+ python-frameworks/openai-agents
+ python-frameworks/llamaindex
+ python-frameworks/google-adk
+)
+
+for dir in "${PACKAGE_DIRS[@]}"; do
+ file="${dir}/pyproject.toml"
+ perl -i -pe "s/^version = \".*\"/version = \"${VERSION}\"/" "$file"
+ if [[ "$dir" != "python" ]]; then
+ perl -i -pe "s/\"sigil-sdk>=.*\"/\"sigil-sdk>=${VERSION}\"/" "$file"
+ fi
+ echo " updated ${file}"
+done
+
+echo "All Python SDK versions bumped to ${VERSION}"
+'''
+
+# --- Mock generation (stubs) ---
+
+[tasks."generate:mocks:sdk-go"]
+description = "Generate Go SDK mocks"
+run = """
+#!/usr/bin/env bash
+set -euo pipefail
+echo "==> generating Go SDK mocks"
+echo "No Go SDK mocks to generate yet."
+"""
+
+[tasks."generate:mocks:sdk-go-providers"]
+description = "Generate Go SDK provider mocks"
+run = """
+#!/usr/bin/env bash
+set -euo pipefail
+echo "==> generating Go SDK provider mocks"
+echo "No Go SDK provider mocks to generate yet."
+"""
+
+[tasks."generate:mocks:sdk-go-frameworks"]
+description = "Generate Go SDK framework mocks"
+run = """
+#!/usr/bin/env bash
+set -euo pipefail
+echo "==> generating Go SDK framework mocks"
+echo "No Go SDK framework mocks to generate yet."
+"""
+
+[tasks."generate:mocks"]
+description = "Generate all mock implementations"
+depends = ["generate:mocks:sdk-go", "generate:mocks:sdk-go-providers", "generate:mocks:sdk-go-frameworks"]
+
+[tasks.check]
+description = "Run lint + typecheck + tests"
+depends = ["lint", "typecheck"]
+run = "mise run test:sdk:all"
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..2c256e4
--- /dev/null
+++ b/package.json
@@ -0,0 +1,4 @@
+{
+ "private": true,
+ "packageManager": "pnpm@10.12.1"
+}
diff --git a/plugins/opencode/.gitignore b/plugins/opencode/.gitignore
new file mode 100644
index 0000000..b947077
--- /dev/null
+++ b/plugins/opencode/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+dist/
diff --git a/plugins/opencode/README.md b/plugins/opencode/README.md
new file mode 100644
index 0000000..d752ac8
--- /dev/null
+++ b/plugins/opencode/README.md
@@ -0,0 +1,41 @@
+# opencode-sigil
+
+OpenCode plugin that records LLM generations to Grafana Sigil for AI observability.
+
+## What it does
+
+Hooks into OpenCode's chat lifecycle to capture assistant messages and send them to Sigil as generation telemetry. Tracks conversation context, tool usage, model metadata, and optionally full message content with PII redaction.
+
+## Setup
+
+1. Create `~/.config/opencode/opencode-sigil.json`:
+
+```json
+{
+ "enabled": true,
+ "endpoint": "http://localhost:8080/api/v1/generations:export",
+ "auth": { "mode": "none" },
+ "agentName": "opencode",
+ "contentCapture": true
+}
+```
+
+2. Register the plugin in your OpenCode configuration.
+
+### Auth modes
+
+- `none` -- no authentication (local dev)
+- `bearer` -- `{ "mode": "bearer", "bearerToken": "..." }`
+- `tenant` -- `{ "mode": "tenant", "tenantId": "..." }`
+- `basic` -- `{ "mode": "basic", "tenantId": "...", "token": "..." }`
+
+## Development
+
+```bash
+# From the repo root
+pnpm install
+pnpm --filter opencode-sigil build
+pnpm --filter opencode-sigil test
+```
+
+The `@grafana/sigil-sdk-js` dependency resolves via pnpm workspace linking to `sdks/js`.
diff --git a/plugins/opencode/package.json b/plugins/opencode/package.json
new file mode 100644
index 0000000..386564e
--- /dev/null
+++ b/plugins/opencode/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "opencode-sigil",
+ "version": "0.1.0",
+ "description": "OpenCode plugin for Grafana Sigil AI telemetry",
+ "type": "module",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js"
+ }
+ },
+ "files": ["dist", "skills"],
+ "scripts": {
+ "build": "bash scripts/build.sh",
+ "typecheck": "tsc --noEmit",
+ "deploy": "bash scripts/deploy.sh",
+ "test": "vitest run",
+ "test:watch": "vitest"
+ },
+ "peerDependencies": {
+ "@opencode-ai/plugin": "^1.2.16"
+ },
+ "devDependencies": {
+ "@grafana/sigil-sdk-js": "workspace:*",
+ "@opencode-ai/plugin": "^1.3.0",
+ "@opencode-ai/sdk": "^1.3.2",
+ "@types/node": "^24.0.0",
+ "esbuild": "^0.27.3",
+"typescript": "^6.0.0",
+ "vitest": "^4.1.0"
+ }
+}
diff --git a/plugins/opencode/scripts/build.sh b/plugins/opencode/scripts/build.sh
new file mode 100755
index 0000000..7161f73
--- /dev/null
+++ b/plugins/opencode/scripts/build.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+SDK_DIR="$(cd "${SCRIPT_DIR}/../../../sdks/js" && pwd)"
+
+# Build the SDK so workspace-linked types are available
+if [ ! -f "${SDK_DIR}/dist/index.d.ts" ]; then
+ echo "Building @grafana/sigil-sdk-js..."
+ npx tsc --project "${SDK_DIR}/tsconfig.build.json"
+fi
+
+tsc --noEmit
+
+npx esbuild src/index.ts \
+ --bundle \
+ --format=esm \
+ --platform=node \
+ --target=es2022 \
+ --outfile=dist/index.js \
+ --external:@opencode-ai/plugin \
+ --external:@opencode-ai/sdk
+
+tsc --emitDeclarationOnly --declaration --declarationMap --outDir dist
diff --git a/plugins/opencode/scripts/deploy.sh b/plugins/opencode/scripts/deploy.sh
new file mode 100755
index 0000000..6594dd0
--- /dev/null
+++ b/plugins/opencode/scripts/deploy.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PLUGIN_DIR="$(dirname "$SCRIPT_DIR")"
+OPENCODE_DIR="${HOME}/.config/opencode"
+
+echo "Deploying opencode-sigil..."
+
+mkdir -p "${OPENCODE_DIR}/plugins" "${OPENCODE_DIR}/skills"
+
+ln -sf "${PLUGIN_DIR}/dist/index.js" "${OPENCODE_DIR}/plugins/opencode-sigil.js"
+echo " [link] opencode-sigil.js"
+
+if [ -d "${PLUGIN_DIR}/skills/sigil" ]; then
+ rm -rf "${OPENCODE_DIR}/skills/sigil"
+ cp -R "${PLUGIN_DIR}/skills/sigil" "${OPENCODE_DIR}/skills/sigil"
+ echo " [copy] sigil skill"
+fi
+
+echo "Done. Restart OpenCode to pick up changes."
diff --git a/plugins/opencode/skills/sigil/SKILL.md b/plugins/opencode/skills/sigil/SKILL.md
new file mode 100644
index 0000000..c7f1b3d
--- /dev/null
+++ b/plugins/opencode/skills/sigil/SKILL.md
@@ -0,0 +1,151 @@
+# Claude Code Prompt: Sigil Instrumentation
+
+You are running in Opencode with repository files and shell access.
+
+- Prefer direct file edits over speculative refactors.
+- Before proposing broad changes, confirm impact scope with quick evidence.
+
+## Sigil Agent-First Instrumentation Brief
+
+You are acting as a coding agent inside this repository. Your goal is to add or improve Grafana Sigil instrumentation with minimal, safe changes.
+
+## Mission
+
+1. Find AI generation and tool/agent execution paths.
+2. Add Sigil instrumentation using the local language SDK where possible.
+3. Preserve behavior and keep diffs small.
+4. Add or update tests for changed instrumentation behavior.
+5. Explain what was instrumented and why.
+
+## Output contract (required)
+
+Return:
+
+- Top opportunities first (highest traffic / highest impact)
+- For each opportunity:
+ - exact file path(s)
+ - why this location matters
+ - concrete diff proposal
+ - test plan
+ - any risk or compatibility concern
+
+## Sigil architecture and ingest model (must follow)
+
+- Sigil uses generation-first ingest:
+ - gRPC: `sigil.v1.GenerationIngestService.ExportGenerations`
+ - HTTP parity: `POST /api/v1/generations:export`
+- Traces/metrics go through OTEL collector/alloy, not through Sigil ingest.
+- Required generation modes:
+ - non-stream: `SYNC`
+ - stream: `STREAM`
+- Raw provider artifacts are default OFF and only enabled for explicit debug opt-in.
+
+Authoritative references in this repo:
+
+- `ARCHITECTURE.md`
+- `docs/references/generation-ingest-contract.md`
+- `docs/references/semantic-conventions.md`
+
+## Telemetry fields to prioritize
+
+On generation and tool spans, capture or preserve these when available:
+
+- identity and routing:
+ - `gen_ai.operation.name`
+ - `sigil.generation.id`
+ - `gen_ai.conversation.id`
+ - `gen_ai.agent.name`
+ - `gen_ai.agent.version`
+ - `sigil.sdk.name`
+- model:
+ - `gen_ai.provider.name`
+ - `gen_ai.request.model`
+ - `gen_ai.response.model`
+- request controls:
+ - `gen_ai.request.max_tokens`
+ - `gen_ai.request.temperature`
+ - `gen_ai.request.top_p`
+ - `sigil.gen_ai.request.tool_choice`
+ - `sigil.gen_ai.request.thinking.enabled`
+ - `sigil.gen_ai.request.thinking.budget_tokens`
+- usage and outcomes:
+ - `gen_ai.usage.input_tokens`
+ - `gen_ai.usage.output_tokens`
+ - `gen_ai.usage.cache_read_input_tokens`
+ - `gen_ai.usage.cache_creation_input_tokens`
+ - `gen_ai.usage.reasoning_tokens`
+ - `gen_ai.response.finish_reasons`
+ - error classification fields (`error.type`, `error.category`)
+
+## SDK locations and how to instrument
+
+Prefer these existing SDKs and wrappers before inventing custom plumbing:
+
+- Go core SDK: `sdks/go` (see `sdks/go/README.md`)
+ - `StartGeneration`, `StartStreamingGeneration`, `StartToolExecution`, `StartEmbedding`
+- JS/TS SDK: `sdks/js` (see `sdks/js/README.md`)
+ - `startGeneration`, `startStreamingGeneration`, `startToolExecution`, `startEmbedding`
+- Python SDK: `sdks/python` (see `sdks/python/README.md`)
+ - `start_generation`, `start_streaming_generation`, `start_tool_execution`, `start_embedding`
+- Java SDK: `sdks/java` (see `sdks/java/README.md`)
+ - `startGeneration`, `startStreamingGeneration`, `withGeneration`, `withToolExecution`
+- .NET SDK: `sdks/dotnet` (see `sdks/dotnet/README.md`)
+ - `StartGeneration`, `StartStreamingGeneration`, `StartToolExecution`, `StartEmbedding`
+
+Provider wrappers and framework adapters already exist; reuse them where possible:
+
+- Go providers: `sdks/go-providers/openai`, `sdks/go-providers/anthropic`, `sdks/go-providers/gemini`
+- Python providers: `sdks/python-providers/*`
+- Java providers: `sdks/java/providers/*`
+- .NET providers: `sdks/dotnet/src/Grafana.Sigil.*`
+- Framework adapters:
+ - Python: `sdks/python-frameworks/*`
+ - Go Google ADK: `sdks/go-frameworks/google-adk`
+ - Java Google ADK: `sdks/java/frameworks/google-adk`
+ - JS subpath adapters documented in `sdks/js/README.md`
+
+## Useful repo examples to copy patterns from
+
+- Go explicit generation flow:
+ - `sdks/go/sigil/example_test.go`
+ - `sdks/go/cmd/devex-emitter/main.go`
+- Go provider wrapper examples:
+ - `sdks/go-providers/openai/sdk_example_test.go`
+ - `sdks/go-providers/anthropic/sdk_example_test.go`
+ - `sdks/go-providers/gemini/sdk_example_test.go`
+- .NET end-to-end emitter:
+ - `sdks/dotnet/examples/Grafana.Sigil.DevExEmitter/Program.cs`
+- JS transport and framework behavior:
+ - `sdks/js/test/client.transport.test.mjs`
+ - `sdks/js/test/frameworks.vercel-ai-sdk.test.mjs`
+- Python framework integration tests:
+ - `sdks/python-frameworks/*/tests/*.py`
+
+## Implementation rules
+
+- Keep behavior unchanged except instrumentation additions/fixes.
+- Prefer small targeted patches over refactors.
+- Use existing conventions in each language package.
+- Keep raw artifacts disabled unless explicitly asked.
+- Ensure non-stream wrappers set `SYNC`, stream wrappers set `STREAM`.
+- Ensure lifecycle flush/shutdown semantics are preserved.
+
+## Validation checklist
+
+After proposing edits, include checks for:
+
+- span attributes emitted as expected
+- generation payload shape valid for ingest contract
+- no regressions in existing tests
+- language-specific tests or focused test additions for new instrumentation logic
+
+## Deliverable format (strict)
+
+Provide:
+
+1. Prioritized instrumentation opportunities
+2. Proposed diffs per opportunity
+3. Test updates per opportunity
+4. Rollout/risk notes
+
+If no safe opportunities are found, explain exactly why and list what evidence you checked.
diff --git a/plugins/opencode/src/client.ts b/plugins/opencode/src/client.ts
new file mode 100644
index 0000000..ed05aa6
--- /dev/null
+++ b/plugins/opencode/src/client.ts
@@ -0,0 +1,67 @@
+import { SigilClient } from "@grafana/sigil-sdk-js";
+import type { SigilConfig, SigilAuthConfig } from "./config.js";
+
+// Matches ExportAuthConfig from @grafana/sigil-sdk-js (not re-exported from package index)
+type ResolvedAuth = {
+ mode: "none" | "tenant" | "bearer";
+ tenantId?: string;
+ bearerToken?: string;
+};
+
+export function resolveEnvVars(value: string): string {
+ return value.replace(/\$\{(\w+)\}/g, (_match, name) => {
+ return process.env[name] ?? "";
+ });
+}
+
+type ResolvedTransport = {
+ auth: ResolvedAuth;
+ headers?: Record;
+};
+
+function resolveAuth(auth: SigilAuthConfig): ResolvedTransport {
+ switch (auth.mode) {
+ case "bearer":
+ return { auth: { mode: "bearer", bearerToken: resolveEnvVars(auth.bearerToken) } };
+ case "tenant":
+ return { auth: { mode: "tenant", tenantId: resolveEnvVars(auth.tenantId) } };
+ case "basic": {
+ // JS SDK doesn't support Basic auth natively — use
+ // mode "none" and inject the Authorization header manually.
+ const user = resolveEnvVars(auth.tenantId);
+ const pass = resolveEnvVars(auth.token);
+ const encoded = Buffer.from(`${user}:${pass}`).toString("base64");
+ return {
+ auth: { mode: "none" },
+ headers: { Authorization: `Basic ${encoded}` },
+ };
+ }
+ case "none":
+ return { auth: { mode: "none" } };
+ }
+}
+
+const GENERATION_EXPORT_PATH = "/api/v1/generations:export";
+
+export function createSigilClient(config: SigilConfig): SigilClient | null {
+ try {
+ if (!config.endpoint.includes(GENERATION_EXPORT_PATH)) {
+ console.warn(
+ `[sigil] endpoint "${config.endpoint}" does not include "${GENERATION_EXPORT_PATH}" -- ` +
+ `the JS SDK requires the full export URL (e.g. "http://localhost:8080${GENERATION_EXPORT_PATH}")`,
+ );
+ }
+ const transport = resolveAuth(config.auth);
+ return new SigilClient({
+ generationExport: {
+ protocol: "http",
+ endpoint: config.endpoint,
+ auth: transport.auth,
+ ...(transport.headers && { headers: transport.headers }),
+ },
+ });
+ } catch {
+ console.warn("[sigil] failed to create SigilClient");
+ return null;
+ }
+}
diff --git a/plugins/opencode/src/config.ts b/plugins/opencode/src/config.ts
new file mode 100644
index 0000000..ae3b1e2
--- /dev/null
+++ b/plugins/opencode/src/config.ts
@@ -0,0 +1,51 @@
+import { readFile } from "fs/promises";
+import { join } from "path";
+import { homedir } from "os";
+
+export type SigilAuthConfig =
+ | { mode: "bearer"; bearerToken: string }
+ | { mode: "tenant"; tenantId: string }
+ | { mode: "basic"; tenantId: string; token: string }
+ | { mode: "none" };
+
+export type SigilConfig = {
+ enabled: boolean;
+ endpoint: string;
+ auth: SigilAuthConfig;
+ agentName?: string;
+ agentVersion?: string;
+ contentCapture?: boolean;
+};
+
+const CONFIG_PATH = join(homedir(), ".config", "opencode", "opencode-sigil.json");
+
+const DISABLED: SigilConfig = {
+ enabled: false,
+ endpoint: "",
+ auth: { mode: "none" },
+};
+
+export async function loadSigilConfig(): Promise {
+ try {
+ const raw = await readFile(CONFIG_PATH, "utf-8");
+ const parsed = JSON.parse(raw);
+ return parseSigilConfig(parsed) ?? DISABLED;
+ } catch {
+ return DISABLED;
+ }
+}
+
+export function parseSigilConfig(raw: unknown): SigilConfig | undefined {
+ if (!raw || typeof raw !== "object") return undefined;
+ const obj = raw as Record;
+ if (obj.enabled !== true) return undefined;
+ if (typeof obj.endpoint !== "string" || !obj.endpoint) {
+ console.warn("[sigil] enabled but endpoint is missing -- disabling");
+ return undefined;
+ }
+ if (!obj.auth || typeof obj.auth !== "object") {
+ console.warn("[sigil] enabled but auth config is missing -- disabling");
+ return undefined;
+ }
+ return raw as SigilConfig;
+}
diff --git a/plugins/opencode/src/hooks.ts b/plugins/opencode/src/hooks.ts
new file mode 100644
index 0000000..9aecfc9
--- /dev/null
+++ b/plugins/opencode/src/hooks.ts
@@ -0,0 +1,189 @@
+import type { SigilClient } from "@grafana/sigil-sdk-js";
+import type { AssistantMessage, UserMessage, Part } from "@opencode-ai/sdk";
+import type { PluginInput } from "@opencode-ai/plugin";
+import type { SigilConfig } from "./config.js";
+import { createSigilClient } from "./client.js";
+import { Redactor } from "./redact.js";
+import { mapGeneration, mapError, mapToolDefinitions } from "./mappers.js";
+
+type OpencodeClient = PluginInput["client"];
+
+// Track recorded messages per session for dedup and cleanup
+const recordedMessages = new Map>();
+
+// Pending generation store: user-side data captured before assistant responds
+type PendingGeneration = {
+ systemPrompt: string | undefined;
+ userParts: Part[];
+ tools: Record | undefined;
+};
+const pendingGenerations = new Map();
+
+function buildAgentName(prefix: string | undefined, mode: string | undefined): string {
+ const base = prefix || "opencode";
+ return mode ? `${base}:${mode}` : base;
+}
+
+/**
+ * Called from the chat.message hook. Stores user-side data for later use
+ * when the assistant message completes.
+ */
+function handleChatMessage(
+ input: { sessionID: string },
+ output: { message: UserMessage; parts: Part[] },
+): void {
+ pendingGenerations.set(input.sessionID, {
+ systemPrompt: output.message.system,
+ userParts: output.parts,
+ tools: output.message.tools,
+ });
+}
+
+async function handleEvent(
+ sigil: SigilClient,
+ config: SigilConfig,
+ client: OpencodeClient,
+ redactor: Redactor,
+ event: { type: string; properties: unknown },
+): Promise {
+ if (event.type !== "message.updated") return;
+
+ const properties = event.properties as { info?: { role?: string } } | undefined;
+ const msg = properties?.info;
+ if (!msg || msg.role !== "assistant") return;
+
+ const assistantMsg = msg as AssistantMessage;
+
+ // Only record terminal messages
+ const isTerminal = assistantMsg.finish || assistantMsg.error || assistantMsg.time.completed;
+ if (!isTerminal) return;
+
+ // Dedup
+ const sessionSet = recordedMessages.get(assistantMsg.sessionID) ?? new Set();
+ if (sessionSet.has(assistantMsg.id)) return;
+ sessionSet.add(assistantMsg.id);
+ recordedMessages.set(assistantMsg.sessionID, sessionSet);
+
+ // Look up pending generation (user-side data)
+ const pending = pendingGenerations.get(assistantMsg.sessionID);
+
+ // Fetch assistant parts via REST
+ let assistantParts: Part[] = [];
+ try {
+ const response = await client.session.message({
+ path: { id: assistantMsg.sessionID, messageID: assistantMsg.id },
+ });
+ assistantParts = response.data?.parts ?? [];
+ } catch {
+ // REST fetch failed — fall back to metadata-only
+ }
+
+ const contentCapture = config.contentCapture ?? true;
+
+ const seed = {
+ conversationId: assistantMsg.sessionID,
+ agentName: buildAgentName(config.agentName, assistantMsg.mode),
+ agentVersion: config.agentVersion,
+ model: { provider: assistantMsg.providerID, name: assistantMsg.modelID },
+ startedAt: new Date(assistantMsg.time.created),
+ ...(contentCapture && {
+ systemPrompt: pending?.systemPrompt,
+ tools: mapToolDefinitions(pending?.tools),
+ }),
+ };
+
+ // When contentCapture is enabled, map full content with redaction;
+ // otherwise fall back to metadata-only result (no message content).
+ const result = contentCapture
+ ? mapGeneration(assistantMsg, pending?.userParts ?? [], assistantParts, redactor)
+ : mapGeneration(assistantMsg, [], [], redactor);
+
+ try {
+ if (assistantMsg.error) {
+ await sigil.startGeneration(seed, async (recorder) => {
+ recorder.setResult(result);
+ recorder.setCallError(mapError(assistantMsg.error!));
+ });
+ } else {
+ await sigil.startGeneration(seed, async (recorder) => {
+ recorder.setResult(result);
+ });
+ }
+ } catch {
+ // Sigil recording failure should never break the plugin
+ }
+
+ // Clean up pending generation
+ pendingGenerations.delete(assistantMsg.sessionID);
+}
+
+async function handleLifecycle(
+ sigil: SigilClient,
+ event: { type: string; properties: unknown },
+): Promise {
+ const type = event.type as string;
+
+ if (type === "session.idle") {
+ try {
+ await sigil.flush();
+ } catch {
+ // flush failure is non-fatal
+ }
+ }
+
+ if (type === "session.deleted") {
+ const properties = event.properties as { info?: { id?: string } } | undefined;
+ const sessionId = properties?.info?.id;
+ if (sessionId) {
+ recordedMessages.delete(sessionId);
+ pendingGenerations.delete(sessionId);
+ }
+ }
+
+ if (type === "global.disposed") {
+ try {
+ await sigil.shutdown();
+ } catch {
+ // shutdown failure is non-fatal
+ }
+ }
+}
+
+export type SigilHooks = {
+ event: (input: { event: { type: string; properties: unknown } }) => Promise;
+ chatMessage: (
+ input: { sessionID: string },
+ output: { message: UserMessage; parts: Part[] },
+ ) => void;
+};
+
+export async function createSigilHooks(
+ config: SigilConfig,
+ client: OpencodeClient,
+): Promise {
+ if (!config.enabled) return null;
+
+ if (!config.endpoint) {
+ console.warn("[sigil] endpoint is required when enabled -- skipping Sigil initialization");
+ return null;
+ }
+
+ const sigil = createSigilClient(config);
+ if (!sigil) return null;
+
+ const redactor = new Redactor();
+
+ process.on("beforeExit", () => {
+ sigil.shutdown().catch(() => {});
+ });
+
+ return {
+ event: async (input) => {
+ await handleEvent(sigil, config, client, redactor, input.event);
+ await handleLifecycle(sigil, input.event);
+ },
+ chatMessage: (input, output) => {
+ handleChatMessage(input, output);
+ },
+ };
+}
diff --git a/plugins/opencode/src/index.ts b/plugins/opencode/src/index.ts
new file mode 100644
index 0000000..88e897b
--- /dev/null
+++ b/plugins/opencode/src/index.ts
@@ -0,0 +1,22 @@
+import type { Plugin } from "@opencode-ai/plugin";
+import { loadSigilConfig } from "./config.js";
+import { createSigilHooks } from "./hooks.js";
+
+export const SigilPlugin: Plugin = async ({ client }) => {
+ const config = await loadSigilConfig();
+ if (!config.enabled) return {};
+
+ const hooks = await createSigilHooks(config, client);
+ if (!hooks) return {};
+
+ return {
+ "chat.message": async (input, output) => {
+ hooks.chatMessage(input, output);
+ },
+ event: async ({ event }) => {
+ await hooks.event({
+ event: event as { type: string; properties: unknown },
+ });
+ },
+ };
+};
diff --git a/plugins/opencode/src/mappers.test.ts b/plugins/opencode/src/mappers.test.ts
new file mode 100644
index 0000000..7ffb0e7
--- /dev/null
+++ b/plugins/opencode/src/mappers.test.ts
@@ -0,0 +1,194 @@
+import { describe, it, expect } from "vitest";
+import { mapGeneration, mapInputMessages, mapOutputMessages, mapToolDefinitions } from "./mappers.js";
+import { Redactor } from "./redact.js";
+import type { AssistantMessage, Part } from "@opencode-ai/sdk";
+
+const redactor = new Redactor();
+
+function makeAssistantMsg(overrides?: Partial): AssistantMessage {
+ return {
+ id: "msg-1",
+ sessionID: "sess-1",
+ role: "assistant",
+ parentID: "parent-1",
+ modelID: "claude-opus-4-20250514",
+ providerID: "anthropic",
+ mode: "code",
+ path: { cwd: "/tmp", root: "/tmp" },
+ cost: 0.01,
+ tokens: { input: 100, output: 50, reasoning: 10, cache: { read: 5, write: 3 } },
+ time: { created: Date.now(), completed: Date.now() + 1000 },
+ finish: "end_turn",
+ ...overrides,
+ } as AssistantMessage;
+}
+
+describe("mapInputMessages", () => {
+ it("maps TextParts to Sigil user messages", () => {
+ const parts = [
+ { id: "p1", sessionID: "s1", messageID: "m1", type: "text" as const, text: "hello world" },
+ ] as Part[];
+ const result = mapInputMessages(parts);
+ expect(result).toHaveLength(1);
+ expect(result[0].role).toBe("user");
+ expect(result[0].parts?.[0]).toEqual({ type: "text", text: "hello world" });
+ });
+
+ it("skips non-text parts", () => {
+ const parts = [
+ { id: "p1", sessionID: "s1", messageID: "m1", type: "file" as const, mime: "image/png", url: "..." },
+ ] as Part[];
+ expect(mapInputMessages(parts)).toHaveLength(0);
+ });
+
+ it("skips text parts with empty or whitespace-only text", () => {
+ const parts = [
+ { id: "p1", sessionID: "s1", messageID: "m1", type: "text" as const, text: "" },
+ { id: "p2", sessionID: "s1", messageID: "m1", type: "text" as const, text: " " },
+ { id: "p3", sessionID: "s1", messageID: "m1", type: "text" as const, text: "\n\t" },
+ { id: "p4", sessionID: "s1", messageID: "m1", type: "text" as const, text: "hello" },
+ ] as Part[];
+ const result = mapInputMessages(parts);
+ expect(result).toHaveLength(1);
+ expect(result[0].parts?.[0]).toEqual({ type: "text", text: "hello" });
+ });
+});
+
+describe("mapOutputMessages", () => {
+ it("maps TextParts with lightweight redaction", () => {
+ const parts = [
+ { id: "p1", sessionID: "s1", messageID: "m1", type: "text" as const, text: "The result is 42" },
+ ] as Part[];
+ const result = mapOutputMessages(parts, redactor);
+ expect(result).toHaveLength(1);
+ expect(result[0].role).toBe("assistant");
+ expect(result[0].parts?.[0]).toEqual({ type: "text", text: "The result is 42" });
+ });
+
+ it("redacts secrets in tool output but not in assistant text (lightweight)", () => {
+ const secretToken = "glc_abcdefghijklmnopqrstuvwxyz1234";
+ const textParts = [
+ { id: "p1", sessionID: "s1", messageID: "m1", type: "text" as const, text: `Found token: ${secretToken}` },
+ ] as Part[];
+ const result = mapOutputMessages(textParts, redactor);
+ // Tier 1 patterns fire even in lightweight mode
+ expect(result[0].parts?.[0]).toHaveProperty("type", "text");
+ const textContent = (result[0].parts?.[0] as any).text;
+ expect(textContent).not.toContain(secretToken);
+ expect(textContent).toContain("[REDACTED:");
+ });
+
+ it("maps completed ToolParts to tool_call + tool_result with full redaction", () => {
+ const parts = [
+ {
+ id: "p1", sessionID: "s1", messageID: "m1", type: "tool" as const,
+ callID: "call-1", tool: "bash",
+ state: {
+ status: "completed" as const,
+ input: { command: "echo test" },
+ output: "test output",
+ title: "Run bash",
+ metadata: {},
+ time: { start: 1000, end: 2000 },
+ },
+ },
+ ] as Part[];
+ const result = mapOutputMessages(parts, redactor);
+ expect(result).toHaveLength(2);
+ expect(result[0].role).toBe("assistant");
+ expect(result[0].parts?.[0].type).toBe("tool_call");
+ expect(result[1].role).toBe("tool");
+ expect(result[1].parts?.[0].type).toBe("tool_result");
+ });
+
+ it("skips text parts with empty or whitespace-only text", () => {
+ const parts = [
+ { id: "p1", sessionID: "s1", messageID: "m1", type: "text" as const, text: "" },
+ { id: "p2", sessionID: "s1", messageID: "m1", type: "text" as const, text: " " },
+ { id: "p3", sessionID: "s1", messageID: "m1", type: "text" as const, text: "actual content" },
+ ] as Part[];
+ const result = mapOutputMessages(parts, redactor);
+ expect(result).toHaveLength(1);
+ expect(result[0].parts?.[0]).toEqual({ type: "text", text: "actual content" });
+ });
+
+ it("skips reasoning parts with empty or whitespace-only text", () => {
+ const parts = [
+ { id: "p1", sessionID: "s1", messageID: "m1", type: "reasoning" as const, text: "", time: { start: 1000 } },
+ { id: "p2", sessionID: "s1", messageID: "m1", type: "reasoning" as const, text: " ", time: { start: 1000 } },
+ { id: "p3", sessionID: "s1", messageID: "m1", type: "reasoning" as const, text: "thinking about it", time: { start: 1000 } },
+ ] as Part[];
+ const result = mapOutputMessages(parts, redactor);
+ expect(result).toHaveLength(1);
+ expect(result[0].parts?.[0]).toEqual({ type: "thinking", thinking: "thinking about it" });
+ });
+
+ it("maps error ToolParts to tool_call + tool_result with is_error flag", () => {
+ const parts = [
+ {
+ id: "p1", sessionID: "s1", messageID: "m1", type: "tool" as const,
+ callID: "call-1", tool: "bash",
+ state: {
+ status: "error" as const,
+ input: { command: "fail" },
+ error: "command failed",
+ metadata: {},
+ time: { start: 1000, end: 2000 },
+ },
+ },
+ ] as Part[];
+ const result = mapOutputMessages(parts, redactor);
+ expect(result).toHaveLength(2);
+ expect(result[0].role).toBe("assistant");
+ expect(result[0].parts?.[0].type).toBe("tool_call");
+ const toolCall = (result[0].parts?.[0] as any).toolCall;
+ expect(toolCall.id).toBe("call-1");
+ expect(toolCall.name).toBe("bash");
+ expect(result[1].role).toBe("tool");
+ expect(result[1].parts?.[0].type).toBe("tool_result");
+ const toolResult = (result[1].parts?.[0] as any).toolResult;
+ expect(toolResult.toolCallId).toBe("call-1");
+ expect(toolResult.isError).toBe(true);
+ expect(toolResult.content).toBe("command failed");
+ });
+});
+
+describe("mapToolDefinitions", () => {
+ it("maps enabled tools to ToolDefinition array, excludes disabled", () => {
+ const tools = { bash: true, read: true, write: false };
+ const result = mapToolDefinitions(tools);
+ expect(result).toHaveLength(2);
+ expect(result.map((t) => t.name)).toContain("bash");
+ expect(result.map((t) => t.name)).toContain("read");
+ expect(result.map((t) => t.name)).not.toContain("write");
+ });
+
+ it("returns empty array for undefined", () => {
+ expect(mapToolDefinitions(undefined)).toEqual([]);
+ });
+});
+
+describe("mapGeneration", () => {
+ it("maps usage tokens and cost from assistant message", () => {
+ const msg = makeAssistantMsg();
+ const userParts = [
+ { id: "p1", sessionID: "s1", messageID: "m1", type: "text" as const, text: "hello" },
+ ] as Part[];
+ const assistantParts = [
+ { id: "p2", sessionID: "s1", messageID: "m2", type: "text" as const, text: "hi there" },
+ ] as Part[];
+ const result = mapGeneration(msg, userParts, assistantParts, redactor);
+ expect(result.input).toHaveLength(1);
+ expect(result.output).toHaveLength(1);
+ expect(result.usage?.inputTokens).toBe(100);
+ expect(result.metadata?.cost).toBe(0.01);
+ });
+
+ it("maps response model, stop reason, and completion timestamp from assistant message", () => {
+ const msg = makeAssistantMsg();
+ const result = mapGeneration(msg, [], [], redactor);
+ expect(result.responseModel).toBe("claude-opus-4-20250514");
+ expect(result.stopReason).toBe("end_turn");
+ expect(result.completedAt).toBeInstanceOf(Date);
+ });
+});
diff --git a/plugins/opencode/src/mappers.ts b/plugins/opencode/src/mappers.ts
new file mode 100644
index 0000000..3177655
--- /dev/null
+++ b/plugins/opencode/src/mappers.ts
@@ -0,0 +1,167 @@
+import type { AssistantMessage, Part } from "@opencode-ai/sdk";
+import type {
+ GenerationResult,
+ Message,
+ ToolDefinition,
+} from "@grafana/sigil-sdk-js";
+import type { Redactor } from "./redact.js";
+
+export type { GenerationResult };
+
+/**
+ * Map user-side parts to Sigil input messages. No redaction applied — user text is the
+ * user's own data and Sigil needs it verbatim for prompt analysis. Tier 1 patterns in
+ * user text (e.g., pasted connection strings) are a known accepted gap; apply redaction
+ * here if this becomes a problem.
+ */
+export function mapInputMessages(parts: Part[]): Message[] {
+ const messages: Message[] = [];
+ for (const part of parts) {
+ if (part.type === "text" && part.text.trim().length > 0) {
+ messages.push({
+ role: "user",
+ parts: [{ type: "text", text: part.text }],
+ });
+ }
+ }
+ return messages;
+}
+
+/** Map assistant-side parts to Sigil output messages with redaction. */
+export function mapOutputMessages(parts: Part[], redactor: Redactor): Message[] {
+ const messages: Message[] = [];
+ for (const part of parts) {
+ switch (part.type) {
+ case "text": {
+ const text = redactor.redactLightweight(part.text);
+ if (text.trim().length > 0) {
+ messages.push({
+ role: "assistant",
+ parts: [{ type: "text", text }],
+ });
+ }
+ break;
+ }
+ case "reasoning": {
+ const thinking = redactor.redactLightweight(part.text);
+ if (thinking.trim().length > 0) {
+ messages.push({
+ role: "assistant",
+ parts: [{ type: "thinking", thinking }],
+ });
+ }
+ break;
+ }
+ case "tool": {
+ const { state } = part;
+ if (state.status === "completed") {
+ messages.push({
+ role: "assistant",
+ parts: [{
+ type: "tool_call",
+ toolCall: {
+ id: part.callID,
+ name: part.tool,
+ inputJSON: redactor.redact(JSON.stringify(state.input ?? {})),
+ },
+ }],
+ });
+ messages.push({
+ role: "tool",
+ parts: [{
+ type: "tool_result",
+ toolResult: {
+ toolCallId: part.callID,
+ name: part.tool,
+ content: redactor.redact(state.output ?? ""),
+ },
+ }],
+ });
+ } else if (state.status === "error") {
+ messages.push({
+ role: "assistant",
+ parts: [{
+ type: "tool_call",
+ toolCall: {
+ id: part.callID,
+ name: part.tool,
+ inputJSON: redactor.redact(JSON.stringify(state.input ?? {})),
+ },
+ }],
+ });
+ messages.push({
+ role: "tool",
+ parts: [{
+ type: "tool_result",
+ toolResult: {
+ toolCallId: part.callID,
+ name: part.tool,
+ content: redactor.redact(state.error ?? "unknown error"),
+ isError: true,
+ },
+ }],
+ });
+ }
+ break;
+ }
+ }
+ }
+ return messages;
+}
+
+/** Convert opencode tool name map to Sigil ToolDefinition array. Only includes enabled tools. */
+export function mapToolDefinitions(
+ tools: Record | undefined,
+): ToolDefinition[] {
+ if (!tools) return [];
+ return Object.entries(tools)
+ .filter(([, enabled]) => enabled)
+ .map(([name]) => ({ name }));
+}
+
+/** Map an AssistantMessage + parts to a Sigil GenerationResult with content. */
+export function mapGeneration(
+ msg: AssistantMessage,
+ userParts: Part[],
+ assistantParts: Part[],
+ redactor: Redactor,
+): GenerationResult {
+ return {
+ input: mapInputMessages(userParts),
+ output: mapOutputMessages(assistantParts, redactor),
+ usage: {
+ inputTokens: msg.tokens.input,
+ outputTokens: msg.tokens.output,
+ reasoningTokens: msg.tokens.reasoning,
+ cacheReadInputTokens: msg.tokens.cache.read,
+ cacheCreationInputTokens: msg.tokens.cache.write,
+ },
+ responseModel: msg.modelID,
+ stopReason: msg.finish,
+ completedAt: msg.time.completed ? new Date(msg.time.completed) : undefined,
+ metadata: {
+ cost: msg.cost,
+ },
+ };
+}
+
+export function mapError(
+ error: NonNullable,
+): Error {
+ switch (error.name) {
+ case "ProviderAuthError":
+ return new Error("provider_auth");
+ case "APIError":
+ return new Error(`api_error: ${error.data.statusCode ?? "unknown"}`);
+ case "MessageOutputLengthError":
+ return new Error("output_length_exceeded");
+ case "MessageAbortedError":
+ return new Error("aborted");
+ case "UnknownError":
+ return new Error("unknown_error");
+ default: {
+ const _exhaustive: never = error;
+ return new Error("unknown_error");
+ }
+ }
+}
diff --git a/plugins/opencode/src/redact.test.ts b/plugins/opencode/src/redact.test.ts
new file mode 100644
index 0000000..5aea6ee
--- /dev/null
+++ b/plugins/opencode/src/redact.test.ts
@@ -0,0 +1,128 @@
+import { describe, it, expect } from "vitest";
+import { Redactor } from "./redact.js";
+
+describe("Redactor", () => {
+ const redactor = new Redactor();
+
+ describe("redact (full — tier 1 + tier 2)", () => {
+ it("redacts Grafana Cloud tokens", () => {
+ const input = "token: glc_abcdefghijklmnopqrstuvwxyz1234";
+ const result = redactor.redact(input);
+ expect(result).not.toContain("glc_abcdefghijklmnopqrstuvwxyz1234");
+ expect(result).toContain("[REDACTED:");
+ });
+
+ it("redacts Grafana service account tokens", () => {
+ const input = "glsa_abcdefghijklmnopqrstuvwxyz1234";
+ const result = redactor.redact(input);
+ expect(result).not.toContain("glsa_");
+ expect(result).toContain("[REDACTED:");
+ });
+
+ it("redacts AWS access keys", () => {
+ const input = "aws_access_key_id = AKIAIOSFODNN7REALKEY";
+ const result = redactor.redact(input);
+ expect(result).not.toContain("AKIAIOSFODNN7REALKEY");
+ expect(result).toContain("[REDACTED:");
+ });
+
+ it("redacts GitHub personal access tokens", () => {
+ const input = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij";
+ const result = redactor.redact(input);
+ expect(result).not.toContain("ghp_");
+ expect(result).toContain("[REDACTED:");
+ });
+
+ it("redacts PEM private keys", () => {
+ const input = `-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA0Z3VS5JJcds3xfn/ygWyF8PbnGy5AhEiS0C5
+-----END RSA PRIVATE KEY-----`;
+ const result = redactor.redact(input);
+ expect(result).not.toContain("MIIEpAIBAAKCAQ");
+ expect(result).toContain("[REDACTED:");
+ });
+
+ it("redacts connection strings with passwords", () => {
+ const input = "postgres://admin:s3cretP4ss@db.example.com:5432/mydb";
+ const result = redactor.redact(input);
+ expect(result).not.toContain("s3cretP4ss");
+ expect(result).toContain("[REDACTED:");
+ });
+
+ it("redacts Anthropic API keys", () => {
+ const input = "sk-ant-api03-" + "a".repeat(93) + "AA";
+ const result = redactor.redact(input);
+ expect(result).not.toContain("sk-ant-api03-");
+ expect(result).toContain("[REDACTED:");
+ });
+
+ it("redacts modern OpenAI project keys (sk-proj-)", () => {
+ const input = "sk-proj-" + "a".repeat(50);
+ const result = redactor.redact(input);
+ expect(result).not.toContain("sk-proj-");
+ expect(result).toContain("[REDACTED:");
+ });
+
+ it("redacts OpenAI service account keys (sk-svcacct-)", () => {
+ const input = "sk-svcacct-" + "b".repeat(50);
+ const result = redactor.redact(input);
+ expect(result).not.toContain("sk-svcacct-");
+ expect(result).toContain("[REDACTED:");
+ });
+
+ it("redacts env file secret values (tier 2)", () => {
+ const input = "DATABASE_PASSWORD=hunter2secret123";
+ const result = redactor.redact(input);
+ expect(result).toContain("DATABASE_PASSWORD=");
+ expect(result).not.toContain("hunter2secret123");
+ expect(result).toContain("[REDACTED:");
+ });
+
+ it("redacts bearer tokens in headers", () => {
+ const input = 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U';
+ const result = redactor.redact(input);
+ expect(result).toContain("[REDACTED:");
+ });
+
+ it("does NOT redact normal text", () => {
+ const input = "The function returns a list of users from the database.";
+ expect(redactor.redact(input)).toBe(input);
+ });
+
+ it("does NOT redact UUIDs", () => {
+ const input = "session-id: 550e8400-e29b-41d4-a716-446655440000";
+ expect(redactor.redact(input)).toBe(input);
+ });
+
+ it("handles empty string", () => {
+ expect(redactor.redact("")).toBe("");
+ });
+
+ it("handles multiple secrets in one string", () => {
+ const input = "key=AKIAIOSFODNN7REALKEY token=glc_abcdefghijklmnopqrstuvwxyz1234";
+ const result = redactor.redact(input);
+ expect(result).not.toContain("AKIAIOSFODNN7REALKEY");
+ expect(result).not.toContain("glc_abcdefghijklmnopqrstuvwxyz1234");
+ });
+ });
+
+ describe("redactLightweight (tier 1 only)", () => {
+ it("redacts Grafana Cloud tokens", () => {
+ const input = "I found the token: glc_abcdefghijklmnopqrstuvwxyz1234";
+ const result = redactor.redactLightweight(input);
+ expect(result).not.toContain("glc_abcdefghijklmnopqrstuvwxyz1234");
+ expect(result).toContain("[REDACTED:");
+ });
+
+ it("does NOT redact env file patterns (tier 2 only)", () => {
+ const input = "The file contains DATABASE_PASSWORD=hunter2secret123";
+ const result = redactor.redactLightweight(input);
+ expect(result).toContain("hunter2secret123");
+ });
+
+ it("does NOT redact normal text", () => {
+ const input = "The API key configuration is stored in the settings panel.";
+ expect(redactor.redactLightweight(input)).toBe(input);
+ });
+ });
+});
diff --git a/plugins/opencode/src/redact.ts b/plugins/opencode/src/redact.ts
new file mode 100644
index 0000000..76b8d94
--- /dev/null
+++ b/plugins/opencode/src/redact.ts
@@ -0,0 +1,102 @@
+/**
+ * Secret redaction engine for Sigil content capture.
+ *
+ * ~20 high-confidence patterns hand-curated from Gitleaks
+ * (https://github.com/gitleaks/gitleaks). Two tiers:
+ * - Tier 1: definite secret formats — used by both redact() and redactLightweight()
+ * - Tier 2: heuristic env patterns — used only by redact()
+ *
+ * Add more patterns when concrete unredacted secrets are observed.
+ */
+
+interface SecretPattern {
+ id: string;
+ regex: RegExp;
+ tier: 1 | 2;
+}
+
+// --- Tier 1: High-confidence patterns (definite secret formats) ---
+const TIER1_PATTERNS: SecretPattern[] = [
+ // Grafana
+ { id: "grafana-cloud-token", regex: /\bglc_[A-Za-z0-9_-]{20,}/g, tier: 1 },
+ { id: "grafana-service-account-token", regex: /\bglsa_[A-Za-z0-9_-]{20,}/g, tier: 1 },
+ // AWS
+ { id: "aws-access-token", regex: /\b(?:A3T[A-Z0-9]|AKIA|ASIA|ABIA|ACCA)[A-Z2-7]{16}\b/g, tier: 1 },
+ // GitHub
+ { id: "github-pat", regex: /\bghp_[A-Za-z0-9_]{36,}/g, tier: 1 },
+ { id: "github-oauth", regex: /\bgho_[A-Za-z0-9_]{36,}/g, tier: 1 },
+ { id: "github-app-token", regex: /\bghs_[A-Za-z0-9_]{36,}/g, tier: 1 },
+ { id: "github-fine-grained-pat", regex: /\bgithub_pat_[A-Za-z0-9_]{82}/g, tier: 1 },
+ // Anthropic
+ { id: "anthropic-api-key", regex: /\bsk-ant-api03-[a-zA-Z0-9_-]{93}AA/g, tier: 1 },
+ { id: "anthropic-admin-key", regex: /\bsk-ant-admin01-[a-zA-Z0-9_-]{93}AA/g, tier: 1 },
+ // OpenAI (legacy format + modern sk-proj-/sk-svcacct- formats)
+ { id: "openai-api-key", regex: /\bsk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20}/g, tier: 1 },
+ { id: "openai-project-key", regex: /\bsk-proj-[a-zA-Z0-9_-]{40,}/g, tier: 1 },
+ { id: "openai-svcacct-key", regex: /\bsk-svcacct-[a-zA-Z0-9_-]{40,}/g, tier: 1 },
+ // GCP
+ { id: "gcp-api-key", regex: /\bAIza[A-Za-z0-9_-]{35}/g, tier: 1 },
+ // PEM private keys
+ { id: "private-key", regex: /-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*?-----END[A-Z ]*PRIVATE KEY-----/g, tier: 1 },
+ // Connection strings with embedded credentials
+ { id: "connection-string", regex: /(?:postgres|mysql|mongodb|redis|amqp):\/\/[^\s'"]+@[^\s'"]+/g, tier: 1 },
+ // Bearer tokens in Authorization headers
+ { id: "bearer-token", regex: /[Bb]earer\s+[A-Za-z0-9_.\-~+/]{20,}={0,3}/g, tier: 1 },
+ // Slack tokens
+ { id: "slack-token", regex: /\bxox[bporas]-[A-Za-z0-9-]{10,}/g, tier: 1 },
+ // Stripe keys
+ { id: "stripe-key", regex: /\b[sr]k_(?:live|test)_[A-Za-z0-9]{20,}/g, tier: 1 },
+ // SendGrid
+ { id: "sendgrid-api-key", regex: /\bSG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}/g, tier: 1 },
+ // Twilio
+ { id: "twilio-api-key", regex: /\bSK[a-f0-9]{32}/g, tier: 1 },
+ // npm tokens
+ { id: "npm-token", regex: /\bnpm_[A-Za-z0-9]{36}/g, tier: 1 },
+ // PyPI tokens
+ { id: "pypi-token", regex: /\bpypi-[A-Za-z0-9_-]{50,}/g, tier: 1 },
+];
+
+// --- Tier 2: Heuristic patterns (env file values) ---
+const TIER2_PATTERNS: SecretPattern[] = [
+ {
+ id: "env-secret-value",
+ regex: /(?<=(?:PASSWORD|SECRET|TOKEN|KEY|CREDENTIAL|API_KEY|PRIVATE_KEY|ACCESS_KEY)\s*[=:]\s*)\S+/gi,
+ tier: 2,
+ },
+];
+
+/**
+ * Note: Pattern arrays are shared by reference across Redactor instances.
+ * This is safe because: (1) there's a single Redactor instance in production,
+ * (2) JS is single-threaded so .replace() completes synchronously, and
+ * (3) lastIndex is reset before each replace call. If this class is ever used
+ * in workers or multiple instances, clone regexes in the constructor.
+ */
+export class Redactor {
+ private tier1 = TIER1_PATTERNS;
+ private tier2 = TIER2_PATTERNS;
+
+ /** Full redaction: tier 1 + tier 2. Use for tool call args and tool results. */
+ redact(text: string): string {
+ let result = text;
+ for (const pattern of this.tier1) {
+ pattern.regex.lastIndex = 0;
+ result = result.replace(pattern.regex, `[REDACTED:${pattern.id}]`);
+ }
+ for (const pattern of this.tier2) {
+ pattern.regex.lastIndex = 0;
+ result = result.replace(pattern.regex, `[REDACTED:${pattern.id}]`);
+ }
+ return result;
+ }
+
+ /** Lightweight redaction: tier 1 only. Use for assistant text and reasoning. */
+ redactLightweight(text: string): string {
+ let result = text;
+ for (const pattern of this.tier1) {
+ pattern.regex.lastIndex = 0;
+ result = result.replace(pattern.regex, `[REDACTED:${pattern.id}]`);
+ }
+ return result;
+ }
+}
diff --git a/plugins/opencode/tsconfig.json b/plugins/opencode/tsconfig.json
new file mode 100644
index 0000000..ce62ced
--- /dev/null
+++ b/plugins/opencode/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ES2022",
+ "moduleResolution": "bundler",
+ "lib": ["ES2022"],
+ "outDir": "dist",
+ "rootDir": "src",
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true
+ },
+ "include": ["src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/plugins/opencode/vitest.config.ts b/plugins/opencode/vitest.config.ts
new file mode 100644
index 0000000..ae847ff
--- /dev/null
+++ b/plugins/opencode/vitest.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ include: ["src/**/*.test.ts"],
+ },
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..5ef2747
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,6556 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .: {}
+
+ js:
+ dependencies:
+ '@anthropic-ai/sdk':
+ specifier: ^0.80.0
+ version: 0.80.0(zod@4.3.6)
+ '@google/adk':
+ specifier: ^0.5.0
+ version: 0.5.0(ee6095569807c0f2faf9175cb5eca775)
+ '@google/genai':
+ specifier: ^1.41.0
+ version: 1.47.0(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))
+ '@grpc/grpc-js':
+ specifier: ^1.14.1
+ version: 1.14.3
+ '@grpc/proto-loader':
+ specifier: ^0.8.0
+ version: 0.8.0
+ '@langchain/core':
+ specifier: ^1.0.0
+ version: 1.1.38(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(openai@6.33.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0)
+ '@langchain/langgraph':
+ specifier: ^1.2.0
+ version: 1.2.6(@langchain/core@1.1.38(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(openai@6.33.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0))(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6)
+ '@openai/agents':
+ specifier: ^0.8.0
+ version: 0.8.2(@cfworker/json-schema@4.1.1)(ws@8.20.0)(zod@4.3.6)
+ '@opentelemetry/api':
+ specifier: ^1.9.0
+ version: 1.9.1
+ '@opentelemetry/exporter-metrics-otlp-grpc':
+ specifier: ^0.213.0
+ version: 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/exporter-metrics-otlp-http':
+ specifier: ^0.213.0
+ version: 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/exporter-trace-otlp-grpc':
+ specifier: ^0.213.0
+ version: 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/exporter-trace-otlp-http':
+ specifier: ^0.213.0
+ version: 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-metrics':
+ specifier: ^2.1.0
+ version: 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-trace-base':
+ specifier: ^2.5.0
+ version: 2.6.1(@opentelemetry/api@1.9.1)
+ llamaindex:
+ specifier: ^0.12.1
+ version: 0.12.1(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(hono@4.12.9)(tree-sitter@0.22.4)(web-tree-sitter@0.24.7)(zod@4.3.6)
+ openai:
+ specifier: ^6.27.0
+ version: 6.33.0(ws@8.20.0)(zod@4.3.6)
+ devDependencies:
+ '@opentelemetry/context-async-hooks':
+ specifier: ^2.6.0
+ version: 2.6.1(@opentelemetry/api@1.9.1)
+ '@types/node':
+ specifier: ^24.11.0
+ version: 24.12.0
+ typescript:
+ specifier: ^6.0.0
+ version: 6.0.2
+
+ plugins/opencode:
+ devDependencies:
+ '@grafana/sigil-sdk-js':
+ specifier: workspace:*
+ version: link:../../js
+ '@opencode-ai/plugin':
+ specifier: ^1.3.0
+ version: 1.3.13
+ '@opencode-ai/sdk':
+ specifier: ^1.3.2
+ version: 1.3.13
+ '@types/node':
+ specifier: ^24.0.0
+ version: 24.12.0
+ esbuild:
+ specifier: ^0.27.3
+ version: 0.27.4
+ typescript:
+ specifier: ^6.0.0
+ version: 6.0.2
+ vitest:
+ specifier: ^4.1.0
+ version: 4.1.2(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.4))
+
+packages:
+
+ '@a2a-js/sdk@0.3.13':
+ resolution: {integrity: sha512-BZr0f9JVNQs3GKOM9xINWCh6OKIJWZFPyqqVqTym5mxO2Eemc6I/0zL7zWnljHzGdaf5aZQyQN5xa6PSH62q+A==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@bufbuild/protobuf': ^2.10.2
+ '@grpc/grpc-js': ^1.11.0
+ express: ^4.21.2 || ^5.1.0
+ peerDependenciesMeta:
+ '@bufbuild/protobuf':
+ optional: true
+ '@grpc/grpc-js':
+ optional: true
+ express:
+ optional: true
+
+ '@anthropic-ai/sdk@0.80.0':
+ resolution: {integrity: sha512-WeXLn7zNVk3yjeshn+xZHvld6AoFUOR3Sep6pSoHho5YbSi6HwcirqgPA5ccFuW8QTVJAAU7N8uQQC6Wa9TG+g==}
+ hasBin: true
+ peerDependencies:
+ zod: ^3.25.0 || ^4.0.0
+ peerDependenciesMeta:
+ zod:
+ optional: true
+
+ '@aws-crypto/sha256-js@5.2.0':
+ resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==}
+ engines: {node: '>=16.0.0'}
+
+ '@aws-crypto/util@5.2.0':
+ resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==}
+
+ '@aws-sdk/types@3.973.6':
+ resolution: {integrity: sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==}
+ engines: {node: '>=20.0.0'}
+
+ '@azure-rest/core-client@2.5.1':
+ resolution: {integrity: sha512-EHaOXW0RYDKS5CFffnixdyRPak5ytiCtU7uXDcP/uiY+A6jFRwNGzzJBiznkCzvi5EYpY+YWinieqHb0oY916A==}
+ engines: {node: '>=20.0.0'}
+
+ '@azure/abort-controller@2.1.2':
+ resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==}
+ engines: {node: '>=18.0.0'}
+
+ '@azure/core-auth@1.10.1':
+ resolution: {integrity: sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==}
+ engines: {node: '>=20.0.0'}
+
+ '@azure/core-client@1.10.1':
+ resolution: {integrity: sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==}
+ engines: {node: '>=20.0.0'}
+
+ '@azure/core-http-compat@2.3.2':
+ resolution: {integrity: sha512-Tf6ltdKzOJEgxZeWLCjMxrxbodB/ZeCbzzA1A2qHbhzAjzjHoBVSUeSl/baT/oHAxhc4qdqVaDKnc2+iE932gw==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@azure/core-client': ^1.10.0
+ '@azure/core-rest-pipeline': ^1.22.0
+
+ '@azure/core-lro@2.7.2':
+ resolution: {integrity: sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==}
+ engines: {node: '>=18.0.0'}
+
+ '@azure/core-paging@1.6.2':
+ resolution: {integrity: sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==}
+ engines: {node: '>=18.0.0'}
+
+ '@azure/core-rest-pipeline@1.23.0':
+ resolution: {integrity: sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@azure/core-tracing@1.3.1':
+ resolution: {integrity: sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@azure/core-util@1.13.1':
+ resolution: {integrity: sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==}
+ engines: {node: '>=20.0.0'}
+
+ '@azure/identity@4.13.1':
+ resolution: {integrity: sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==}
+ engines: {node: '>=20.0.0'}
+
+ '@azure/keyvault-common@2.0.0':
+ resolution: {integrity: sha512-wRLVaroQtOqfg60cxkzUkGKrKMsCP6uYXAOomOIysSMyt1/YM0eUn9LqieAWM8DLcU4+07Fio2YGpPeqUbpP9w==}
+ engines: {node: '>=18.0.0'}
+
+ '@azure/keyvault-keys@4.10.0':
+ resolution: {integrity: sha512-eDT7iXoBTRZ2n3fLiftuGJFD+yjkiB1GNqzU2KbY1TLYeXeSPVTVgn2eJ5vmRTZ11978jy2Kg2wI7xa9Tyr8ag==}
+ engines: {node: '>=18.0.0'}
+
+ '@azure/logger@1.3.0':
+ resolution: {integrity: sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==}
+ engines: {node: '>=20.0.0'}
+
+ '@azure/msal-browser@5.6.2':
+ resolution: {integrity: sha512-ZgcN9ToRJ80f+wNPBBKYJ+DG0jlW7ktEjYtSNkNsTrlHVMhKB8tKMdI1yIG1I9BJtykkXtqnuOjlJaEMC7J6aw==}
+ engines: {node: '>=0.8.0'}
+
+ '@azure/msal-common@16.4.0':
+ resolution: {integrity: sha512-twXt09PYtj1PffNNIAzQlrBd0DS91cdA6i1gAfzJ6BnPM4xNk5k9q/5xna7jLIjU3Jnp0slKYtucshGM8OGNAw==}
+ engines: {node: '>=0.8.0'}
+
+ '@azure/msal-node@5.1.1':
+ resolution: {integrity: sha512-71grXU6+5hl+3CL3joOxlj/AW6rmhthuTlG0fRqsTrhPArQBpZuUFzCIlKOGdcafLUa/i1hBdV78ZxJdlvRA+g==}
+ engines: {node: '>=20'}
+
+ '@babel/runtime@7.29.2':
+ resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
+ engines: {node: '>=6.9.0'}
+
+ '@cfworker/json-schema@4.1.1':
+ resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==}
+
+ '@colors/colors@1.6.0':
+ resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
+ engines: {node: '>=0.1.90'}
+
+ '@dabh/diagnostics@2.0.8':
+ resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==}
+
+ '@emnapi/core@1.9.1':
+ resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==}
+
+ '@emnapi/runtime@1.9.1':
+ resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==}
+
+ '@emnapi/wasi-threads@1.2.0':
+ resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==}
+
+ '@esbuild/aix-ppc64@0.27.4':
+ resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.27.4':
+ resolution: {integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.27.4':
+ resolution: {integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.27.4':
+ resolution: {integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.27.4':
+ resolution: {integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.27.4':
+ resolution: {integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.27.4':
+ resolution: {integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.27.4':
+ resolution: {integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.27.4':
+ resolution: {integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.27.4':
+ resolution: {integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.27.4':
+ resolution: {integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.27.4':
+ resolution: {integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.27.4':
+ resolution: {integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.27.4':
+ resolution: {integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.27.4':
+ resolution: {integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.27.4':
+ resolution: {integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.27.4':
+ resolution: {integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.27.4':
+ resolution: {integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.27.4':
+ resolution: {integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.27.4':
+ resolution: {integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.27.4':
+ resolution: {integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.27.4':
+ resolution: {integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.27.4':
+ resolution: {integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.27.4':
+ resolution: {integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.27.4':
+ resolution: {integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.27.4':
+ resolution: {integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@finom/zod-to-json-schema@3.24.11':
+ resolution: {integrity: sha512-fL656yBPiWebtfGItvtXLWrFNGlF1NcDFS0WdMQXMs9LluVg0CfT5E2oXYp0pidl0vVG53XkW55ysijNkU5/hA==}
+ deprecated: 'Use https://www.npmjs.com/package/zod-v3-to-json-schema instead. See issue comment for details: https://github.com/StefanTerdell/zod-to-json-schema/issues/178#issuecomment-3533122539'
+ peerDependencies:
+ zod: ^4.0.14
+
+ '@gar/promisify@1.1.3':
+ resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
+
+ '@google-cloud/opentelemetry-cloud-monitoring-exporter@0.21.0':
+ resolution: {integrity: sha512-+lAew44pWt6rA4l8dQ1gGhH7Uo95wZKfq/GBf9aEyuNDDLQ2XppGEEReu6ujesSqTtZ8ueQFt73+7SReSHbwqg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@opentelemetry/api': ^1.9.0
+ '@opentelemetry/core': ^2.0.0
+ '@opentelemetry/resources': ^2.0.0
+ '@opentelemetry/sdk-metrics': ^2.0.0
+
+ '@google-cloud/opentelemetry-cloud-trace-exporter@3.0.0':
+ resolution: {integrity: sha512-mUfLJBFo+ESbO0dAGboErx2VyZ7rbrHcQvTP99yH/J72dGaPbH2IzS+04TFbTbEd1VW5R9uK3xq2CqawQaG+1Q==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@opentelemetry/api': ^1.0.0
+ '@opentelemetry/core': ^2.0.0
+ '@opentelemetry/resources': ^2.0.0
+ '@opentelemetry/sdk-trace-base': ^2.0.0
+
+ '@google-cloud/opentelemetry-resource-util@3.0.0':
+ resolution: {integrity: sha512-CGR/lNzIfTKlZoZFfS6CkVzx+nsC9gzy6S8VcyaLegfEJbiPjxbMLP7csyhJTvZe/iRRcQJxSk0q8gfrGqD3/Q==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@opentelemetry/core': ^2.0.0
+ '@opentelemetry/resources': ^2.0.0
+
+ '@google-cloud/paginator@5.0.2':
+ resolution: {integrity: sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==}
+ engines: {node: '>=14.0.0'}
+
+ '@google-cloud/precise-date@4.0.0':
+ resolution: {integrity: sha512-1TUx3KdaU3cN7nfCdNf+UVqA/PSX29Cjcox3fZZBtINlRrXVTmUkQnCKv2MbBUbCopbK4olAT1IHl76uZyCiVA==}
+ engines: {node: '>=14.0.0'}
+
+ '@google-cloud/projectify@4.0.0':
+ resolution: {integrity: sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==}
+ engines: {node: '>=14.0.0'}
+
+ '@google-cloud/promisify@4.0.0':
+ resolution: {integrity: sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==}
+ engines: {node: '>=14'}
+
+ '@google-cloud/storage@7.19.0':
+ resolution: {integrity: sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ==}
+ engines: {node: '>=14'}
+
+ '@google/adk@0.5.0':
+ resolution: {integrity: sha512-VQbiGayQewKm3IXJIUX2iq+eqY7WHd71MOT0XwPJTXnViuo3/ysiwxCQ2D8sxAGQt+fol8YHaHxBUkoIpqOBzA==}
+ peerDependencies:
+ '@google-cloud/opentelemetry-cloud-monitoring-exporter': ^0.21.0
+ '@google-cloud/opentelemetry-cloud-trace-exporter': ^3.0.0
+ '@google-cloud/storage': ^7.17.1
+ '@mikro-orm/mariadb': ^6.6.6
+ '@mikro-orm/mssql': ^6.6.6
+ '@mikro-orm/mysql': ^6.6.6
+ '@mikro-orm/postgresql': ^6.6.6
+ '@mikro-orm/sqlite': ^6.6.6
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/api-logs': ^0.205.0
+ '@opentelemetry/exporter-logs-otlp-http': ^0.205.0
+ '@opentelemetry/exporter-metrics-otlp-http': ^0.205.0
+ '@opentelemetry/exporter-trace-otlp-http': ^0.205.0
+ '@opentelemetry/resource-detector-gcp': ^0.40.0
+ '@opentelemetry/resources': ^2.1.0
+ '@opentelemetry/sdk-logs': ^0.205.0
+ '@opentelemetry/sdk-metrics': ^2.1.0
+ '@opentelemetry/sdk-trace-base': ^2.1.0
+ '@opentelemetry/sdk-trace-node': ^2.1.0
+
+ '@google/genai@1.47.0':
+ resolution: {integrity: sha512-0VV7AaXm5rQu3oRHNZNEubRAOL2lv5u+YA72eWnDwcOx3B1jFRbvtgL4drRHlocRHOnludvr3xmbQGbR+/RQAQ==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@modelcontextprotocol/sdk': ^1.25.2
+ peerDependenciesMeta:
+ '@modelcontextprotocol/sdk':
+ optional: true
+
+ '@grpc/grpc-js@1.14.3':
+ resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==}
+ engines: {node: '>=12.10.0'}
+
+ '@grpc/proto-loader@0.8.0':
+ resolution: {integrity: sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ '@hono/node-server@1.19.12':
+ resolution: {integrity: sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw==}
+ engines: {node: '>=18.14.1'}
+ peerDependencies:
+ hono: ^4
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@js-joda/core@5.7.0':
+ resolution: {integrity: sha512-WBu4ULVVxySLLzK1Ppq+OdfP+adRS4ntmDQT915rzDJ++i95gc2jZkM5B6LWEAwN3lGXpfie3yPABozdD3K3Vg==}
+
+ '@js-sdsl/ordered-map@4.4.2':
+ resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==}
+
+ '@langchain/core@1.1.38':
+ resolution: {integrity: sha512-C340wH1YL10CiVOFlEpQMp0zQE85/eBLKX/gi1Lv7shAyUmR3CQ0t/mXlCd5RsZa6ntAN1kDJnp64ArWey9XAA==}
+ engines: {node: '>=20'}
+
+ '@langchain/langgraph-checkpoint@1.0.1':
+ resolution: {integrity: sha512-HM0cJLRpIsSlWBQ/xuDC67l52SqZ62Bh2Y61DX+Xorqwoh5e1KxYvfCD7GnSTbWWhjBOutvnR0vPhu4orFkZfw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@langchain/core': ^1.0.1
+
+ '@langchain/langgraph-sdk@1.8.3':
+ resolution: {integrity: sha512-Py0S5yVtlOHz410aEsSGLRKjtsK2giDvfPS1JjAjTdcs71khuJufFtUZFwmwdJCbsG4DaGurRLHOAJu9jO4a0g==}
+ peerDependencies:
+ '@langchain/core': ^1.1.16
+ react: ^18 || ^19
+ react-dom: ^18 || ^19
+ svelte: ^4.0.0 || ^5.0.0
+ vue: ^3.0.0
+ peerDependenciesMeta:
+ '@langchain/core':
+ optional: true
+ react:
+ optional: true
+ react-dom:
+ optional: true
+ svelte:
+ optional: true
+ vue:
+ optional: true
+
+ '@langchain/langgraph@1.2.6':
+ resolution: {integrity: sha512-5cX402dNGN6w9+0mlMU2dgUiKx6Y1tlENp7x05e9ByDbQCHSDc0kyqRWNFLGc7vatQ92S4ylxQrcCJvi8Fr4SQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@langchain/core': ^1.1.16
+ zod: ^3.25.32 || ^4.2.0
+ zod-to-json-schema: ^3.x
+ peerDependenciesMeta:
+ zod-to-json-schema:
+ optional: true
+
+ '@llamaindex/core@0.6.22':
+ resolution: {integrity: sha512-/BXyemkvpxMaUhOkbwJ2PTvzKjSWkL8+6QLpz/n+pk8xBwMMe1GVBgli/J57gCyi8GbrlBafBj6GaPOgWub2Eg==}
+
+ '@llamaindex/env@0.1.30':
+ resolution: {integrity: sha512-y6kutMcCevzbmexUgz+HXf7KiZemzAoFEYSjAILfR+cG6FmYSF8XvLbGOB34Kx8mlRi7EI8rZXpezJ5qCqOyZg==}
+ peerDependencies:
+ '@huggingface/transformers': ^3.5.0
+ gpt-tokenizer: ^2.5.0
+ peerDependenciesMeta:
+ '@huggingface/transformers':
+ optional: true
+ gpt-tokenizer:
+ optional: true
+
+ '@llamaindex/node-parser@2.0.22':
+ resolution: {integrity: sha512-uj5O89WShAAyiSZ8f8tU7hnLJ6pSmlY2a6hkAOs8odkUgT87dEqaPHpsK7w0iJdEFiob7GoLeRhv2K624FooXg==}
+ peerDependencies:
+ '@llamaindex/core': 0.6.22
+ '@llamaindex/env': 0.1.30
+ tree-sitter: ^0.22.0
+ web-tree-sitter: ^0.24.3
+
+ '@llamaindex/workflow-core@1.3.4':
+ resolution: {integrity: sha512-nDQ61VEYY5lTJFLHZzN7swdpbYrkoqLHKmct/KXF0wZN2Ih7sB4jk9eaVqWbd6zmkkOADT84I6eSd7hSY9kurg==}
+ deprecated: 'This package is deprecated. Please use LlamaAgents (Python Workflows) instead: https://developers.llamaindex.ai/python/llamaagents/overview/'
+ peerDependencies:
+ '@modelcontextprotocol/sdk': ^1.7.0
+ hono: ^4.7.4
+ next: ^15.2.2
+ p-retry: ^6.2.1
+ rxjs: ^7.8.2
+ zod: ^3.25.0 || ^4.0.0
+ peerDependenciesMeta:
+ '@modelcontextprotocol/sdk':
+ optional: true
+ hono:
+ optional: true
+ next:
+ optional: true
+ p-retry:
+ optional: true
+ rxjs:
+ optional: true
+ zod:
+ optional: true
+
+ '@llamaindex/workflow@1.1.24':
+ resolution: {integrity: sha512-VyKsbRkFlnT5dRNKbgLXQV+ZpQ+CAFgmC9LaZv6hD/fIKo6wq1wQW/ZqLZgZt569xeHgxmrXPB6KHdqn/AhPbQ==}
+ peerDependencies:
+ '@llamaindex/core': 0.6.22
+ '@llamaindex/env': 0.1.30
+
+ '@mikro-orm/core@6.6.11':
+ resolution: {integrity: sha512-+edc3ctapRi0lyb2B0+QfUpoWkNmXOcaApDT6RhBxyFo74bpoU/tEb9aMobemN86VhAt/rjM1KDKbJYLM9lxTg==}
+ engines: {node: '>= 18.12.0'}
+
+ '@mikro-orm/knex@6.6.11':
+ resolution: {integrity: sha512-MUxqw+3COpcM06DC3ufW4Aov5RZWpW1Rv/kMfJkHQX+bO81jPdinXkRtx1l8EVWFRiLJEB+3MNhptFQRlmJNXA==}
+ engines: {node: '>= 18.12.0'}
+ peerDependencies:
+ '@mikro-orm/core': ^6.0.0
+ better-sqlite3: '*'
+ libsql: '*'
+ mariadb: '*'
+ peerDependenciesMeta:
+ better-sqlite3:
+ optional: true
+ libsql:
+ optional: true
+ mariadb:
+ optional: true
+
+ '@mikro-orm/mariadb@6.6.11':
+ resolution: {integrity: sha512-TPUFGJHGPGiQC2LE263iyyBbaG1nwSsa6UVQ8ma2QFxLRt62XqGFEw7XJ1uUXXoqZn/4RW8jrIAWFgbrmfnx3g==}
+ engines: {node: '>= 18.12.0'}
+ peerDependencies:
+ '@mikro-orm/core': ^6.0.0
+
+ '@mikro-orm/mssql@6.6.11':
+ resolution: {integrity: sha512-LjMiObzrwKJw9Pt/VUgAgsNU3j/FDZjW8wxvD8522rPdXnNyHtNBDAQQhdWt/e+yJr4bZh7HhoQYekH8Z9G6Yg==}
+ engines: {node: '>= 18.12.0'}
+ peerDependencies:
+ '@mikro-orm/core': ^6.0.0
+
+ '@mikro-orm/mysql@6.6.11':
+ resolution: {integrity: sha512-SPtBLl82Qq+pKx/d5rF276LosKz6JO7D8vTaeudadk6/zlXjpE3SciGmyvt5/+htzts4k348F8zQMCf297NdzQ==}
+ engines: {node: '>= 18.12.0'}
+ peerDependencies:
+ '@mikro-orm/core': ^6.0.0
+
+ '@mikro-orm/postgresql@6.6.11':
+ resolution: {integrity: sha512-YIQroXsAPXRJc3ruk8M5ynbQEQtGUO0Swjb/MMtjn5o9qypqmPBoq4ANCwUY9P2jVlmheQM1O5VK/1OBm7/EVg==}
+ engines: {node: '>= 18.12.0'}
+ peerDependencies:
+ '@mikro-orm/core': ^6.0.0
+
+ '@mikro-orm/reflection@6.6.11':
+ resolution: {integrity: sha512-EG8C79sOzkvqiI1Kvig2TO1ME1YlhxVGLDQaKQur2xUIR31U0cmjWIWd449lCD4mLdrUj1sem7WULLmo2tj7UA==}
+ engines: {node: '>= 18.12.0'}
+ peerDependencies:
+ '@mikro-orm/core': ^6.0.0
+
+ '@mikro-orm/sqlite@6.6.11':
+ resolution: {integrity: sha512-WCO9w6JERp7qMRJKXoNF1ELrQ6PrMBU24EwDdhkY8LH76uqDM4jtfSbIcBDafORiZG/D+Rs8JshS1qEQEX9x7w==}
+ engines: {node: '>= 18.12.0'}
+ peerDependencies:
+ '@mikro-orm/core': ^6.0.0
+
+ '@modelcontextprotocol/sdk@1.29.0':
+ resolution: {integrity: sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@cfworker/json-schema': ^4.1.1
+ zod: ^3.25 || ^4.0
+ peerDependenciesMeta:
+ '@cfworker/json-schema':
+ optional: true
+
+ '@napi-rs/wasm-runtime@1.1.2':
+ resolution: {integrity: sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==}
+ peerDependencies:
+ '@emnapi/core': ^1.7.1
+ '@emnapi/runtime': ^1.7.1
+
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
+ '@npmcli/fs@1.1.1':
+ resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==}
+
+ '@npmcli/move-file@1.1.2':
+ resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==}
+ engines: {node: '>=10'}
+ deprecated: This functionality has been moved to @npmcli/fs
+
+ '@openai/agents-core@0.8.2':
+ resolution: {integrity: sha512-oxp8XmdFcZwturpfWzqpW/2doNJ75FHwYDfZdBxfSK4Q2vmwLsx9wNPmU67i8dubWUXmIzOSXNUCbdL6+iLNlg==}
+ peerDependencies:
+ zod: ^4.0.0
+ peerDependenciesMeta:
+ zod:
+ optional: true
+
+ '@openai/agents-openai@0.8.2':
+ resolution: {integrity: sha512-h81JngBj2EcFDPDTnlKZqWp6JZN/SdR4MPR+f6NKYpqNjDWwJQGpq+w4lguLORUrZmXKWwy7xSZwAJzPB0EoCQ==}
+ peerDependencies:
+ zod: ^4.0.0
+
+ '@openai/agents-realtime@0.8.2':
+ resolution: {integrity: sha512-aY1BGOkhpbb+aq+bU3OkwRMr/CXEc0LZxPFw7vn28NTcVWn2rKdeDEkY2k1EbUv35vk5wo8bwI2LJkQfm+d6uw==}
+ peerDependencies:
+ zod: ^4.0.0
+
+ '@openai/agents@0.8.2':
+ resolution: {integrity: sha512-chxJPncuVbOqAUUpxUuVnT2tZTIr82hD9eVA59GaNzM0uG13fjaiIYgbNpWpAz9w5jQh84HMybWzXL9QNp7daA==}
+ peerDependencies:
+ zod: ^4.0.0
+
+ '@opencode-ai/plugin@1.3.13':
+ resolution: {integrity: sha512-zHgtWfdDz8Wu8srE8f8HUtPT9i6c3jTmgQKoFZUZ+RR5CMQF1kAlb1cxeEe9Xm2DRNFVJog9Cv/G1iUHYgXSUQ==}
+ peerDependencies:
+ '@opentui/core': '>=0.1.95'
+ '@opentui/solid': '>=0.1.95'
+ peerDependenciesMeta:
+ '@opentui/core':
+ optional: true
+ '@opentui/solid':
+ optional: true
+
+ '@opencode-ai/sdk@1.3.13':
+ resolution: {integrity: sha512-/M6HlNnba+xf1EId6qFb2tG0cvq0db3PCQDug1glrf8wYOU57LYNF8WvHX9zoDKPTMv0F+O4pcP/8J+WvDaxHA==}
+
+ '@opentelemetry/api-logs@0.205.0':
+ resolution: {integrity: sha512-wBlPk1nFB37Hsm+3Qy73yQSobVn28F4isnWIBvKpd5IUH/eat8bwcL02H9yzmHyyPmukeccSl2mbN5sDQZYnPg==}
+ engines: {node: '>=8.0.0'}
+
+ '@opentelemetry/api-logs@0.213.0':
+ resolution: {integrity: sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==}
+ engines: {node: '>=8.0.0'}
+
+ '@opentelemetry/api@1.9.1':
+ resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==}
+ engines: {node: '>=8.0.0'}
+
+ '@opentelemetry/context-async-hooks@2.6.1':
+ resolution: {integrity: sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.0.0 <1.10.0'
+
+ '@opentelemetry/core@2.1.0':
+ resolution: {integrity: sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.0.0 <1.10.0'
+
+ '@opentelemetry/core@2.6.0':
+ resolution: {integrity: sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.0.0 <1.10.0'
+
+ '@opentelemetry/core@2.6.1':
+ resolution: {integrity: sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.0.0 <1.10.0'
+
+ '@opentelemetry/exporter-logs-otlp-http@0.205.0':
+ resolution: {integrity: sha512-5JteMyVWiro4ghF0tHQjfE6OJcF7UBUcoEqX3UIQ5jutKP1H+fxFdyhqjjpmeHMFxzOHaYuLlNR1Bn7FOjGyJg==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.3.0
+
+ '@opentelemetry/exporter-metrics-otlp-grpc@0.213.0':
+ resolution: {integrity: sha512-Z8gYKUAU48qwm+a1tjnGv9xbE7a5lukVIwgF6Z5i3VPXPVMe4Sjra0nN3zU7m277h+V+ZpsPGZJ2Xf0OTkL7/w==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.3.0
+
+ '@opentelemetry/exporter-metrics-otlp-http@0.213.0':
+ resolution: {integrity: sha512-yw3fTIw4KQIRXC/ZyYQq5gtA3Ogfdfz/g5HVgleobQAcjUUE8Nj3spGMx8iQPp+S+u6/js7BixufRkXhzLmpJA==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.3.0
+
+ '@opentelemetry/exporter-trace-otlp-grpc@0.213.0':
+ resolution: {integrity: sha512-L8y6piP4jBIIx1Nv7/9hkx25ql6/Cro/kQrs+f9e8bPF0Ar5Dm991v7PnbtubKz6Q4fT872H56QXUWVnz/Cs4Q==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.3.0
+
+ '@opentelemetry/exporter-trace-otlp-http@0.213.0':
+ resolution: {integrity: sha512-tnRmJD39aWrE/Sp7F6AbRNAjKHToDkAqBi6i0lESpGWz3G+f4bhVAV6mgSXH2o18lrDVJXo6jf9bAywQw43wRA==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.3.0
+
+ '@opentelemetry/otlp-exporter-base@0.205.0':
+ resolution: {integrity: sha512-2MN0C1IiKyo34M6NZzD6P9Nv9Dfuz3OJ3rkZwzFmF6xzjDfqqCTatc9v1EpNfaP55iDOCLHFyYNCgs61FFgtUQ==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.3.0
+
+ '@opentelemetry/otlp-exporter-base@0.213.0':
+ resolution: {integrity: sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.3.0
+
+ '@opentelemetry/otlp-grpc-exporter-base@0.213.0':
+ resolution: {integrity: sha512-XgRGuLE9usFNlnw2lgMIM4HTwpcIyjdU/xPoJ8v3LbBLBfjaDkIugjc9HoWa7ZSJ/9Bhzgvm/aD0bGdYUFgnTw==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.3.0
+
+ '@opentelemetry/otlp-transformer@0.205.0':
+ resolution: {integrity: sha512-KmObgqPtk9k/XTlWPJHdMbGCylRAmMJNXIRh6VYJmvlRDMfe+DonH41G7eenG8t4FXn3fxOGh14o/WiMRR6vPg==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.3.0
+
+ '@opentelemetry/otlp-transformer@0.213.0':
+ resolution: {integrity: sha512-RSuAlxFFPjeK4d5Y6ps8L2WhaQI6CXWllIjvo5nkAlBpmq2XdYWEBGiAbOF4nDs8CX4QblJDv5BbMUft3sEfDw==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.3.0
+
+ '@opentelemetry/resource-detector-gcp@0.40.3':
+ resolution: {integrity: sha512-C796YjBA5P1JQldovApYfFA/8bQwFfpxjUbOtGhn1YZkVTLoNQN+kvBwgALfTPWzug6fWsd0xhn9dzeiUcndag==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': ^1.0.0
+
+ '@opentelemetry/resources@2.1.0':
+ resolution: {integrity: sha512-1CJjf3LCvoefUOgegxi8h6r4B/wLSzInyhGP2UmIBYNlo4Qk5CZ73e1eEyWmfXvFtm1ybkmfb2DqWvspsYLrWw==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.3.0 <1.10.0'
+
+ '@opentelemetry/resources@2.6.0':
+ resolution: {integrity: sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.3.0 <1.10.0'
+
+ '@opentelemetry/resources@2.6.1':
+ resolution: {integrity: sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.3.0 <1.10.0'
+
+ '@opentelemetry/sdk-logs@0.205.0':
+ resolution: {integrity: sha512-nyqhNQ6eEzPWQU60Nc7+A5LIq8fz3UeIzdEVBQYefB4+msJZ2vuVtRuk9KxPMw1uHoHDtYEwkr2Ct0iG29jU8w==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.4.0 <1.10.0'
+
+ '@opentelemetry/sdk-logs@0.213.0':
+ resolution: {integrity: sha512-00xlU3GZXo3kXKve4DLdrAL0NAFUaZ9appU/mn00S/5kSUdAvyYsORaDUfR04Mp2CLagAOhrzfUvYozY/EZX2g==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.4.0 <1.10.0'
+
+ '@opentelemetry/sdk-metrics@2.1.0':
+ resolution: {integrity: sha512-J9QX459mzqHLL9Y6FZ4wQPRZG4TOpMCyPOh6mkr/humxE1W2S3Bvf4i75yiMW9uyed2Kf5rxmLhTm/UK8vNkAw==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.9.0 <1.10.0'
+
+ '@opentelemetry/sdk-metrics@2.6.0':
+ resolution: {integrity: sha512-CicxWZxX6z35HR83jl+PLgtFgUrKRQ9LCXyxgenMnz5A1lgYWfAog7VtdOvGkJYyQgMNPhXQwkYrDLujk7z1Iw==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.9.0 <1.10.0'
+
+ '@opentelemetry/sdk-metrics@2.6.1':
+ resolution: {integrity: sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.9.0 <1.10.0'
+
+ '@opentelemetry/sdk-trace-base@2.1.0':
+ resolution: {integrity: sha512-uTX9FBlVQm4S2gVQO1sb5qyBLq/FPjbp+tmGoxu4tIgtYGmBYB44+KX/725RFDe30yBSaA9Ml9fqphe1hbUyLQ==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.3.0 <1.10.0'
+
+ '@opentelemetry/sdk-trace-base@2.6.0':
+ resolution: {integrity: sha512-g/OZVkqlxllgFM7qMKqbPV9c1DUPhQ7d4n3pgZFcrnrNft9eJXZM2TNHTPYREJBrtNdRytYyvwjgL5geDKl3EQ==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.3.0 <1.10.0'
+
+ '@opentelemetry/sdk-trace-base@2.6.1':
+ resolution: {integrity: sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.3.0 <1.10.0'
+
+ '@opentelemetry/sdk-trace-node@2.6.1':
+ resolution: {integrity: sha512-Hh2i4FwHWRFhnO2Q/p6svMxy8MPsNCG0uuzUY3glqm0rwM0nQvbTO1dXSp9OqQoTKXcQzaz9q1f65fsurmOhNw==}
+ engines: {node: ^18.19.0 || >=20.6.0}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.0.0 <1.10.0'
+
+ '@opentelemetry/semantic-conventions@1.40.0':
+ resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==}
+ engines: {node: '>=14'}
+
+ '@oxc-project/types@0.122.0':
+ resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==}
+
+ '@protobufjs/aspromise@1.1.2':
+ resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
+
+ '@protobufjs/base64@1.1.2':
+ resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
+
+ '@protobufjs/codegen@2.0.4':
+ resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==}
+
+ '@protobufjs/eventemitter@1.1.0':
+ resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==}
+
+ '@protobufjs/fetch@1.1.0':
+ resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==}
+
+ '@protobufjs/float@1.0.2':
+ resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
+
+ '@protobufjs/inquire@1.1.0':
+ resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==}
+
+ '@protobufjs/path@1.1.2':
+ resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
+
+ '@protobufjs/pool@1.1.0':
+ resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
+
+ '@protobufjs/utf8@1.1.0':
+ resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
+
+ '@rolldown/binding-android-arm64@1.0.0-rc.12':
+ resolution: {integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [android]
+
+ '@rolldown/binding-darwin-arm64@1.0.0-rc.12':
+ resolution: {integrity: sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rolldown/binding-darwin-x64@1.0.0-rc.12':
+ resolution: {integrity: sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rolldown/binding-freebsd-x64@1.0.0-rc.12':
+ resolution: {integrity: sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12':
+ resolution: {integrity: sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12':
+ resolution: {integrity: sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12':
+ resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12':
+ resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12':
+ resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12':
+ resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@rolldown/binding-linux-x64-musl@1.0.0-rc.12':
+ resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@rolldown/binding-openharmony-arm64@1.0.0-rc.12':
+ resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rolldown/binding-wasm32-wasi@1.0.0-rc.12':
+ resolution: {integrity: sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
+ '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12':
+ resolution: {integrity: sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12':
+ resolution: {integrity: sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@rolldown/pluginutils@1.0.0-rc.12':
+ resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==}
+
+ '@selderee/plugin-htmlparser2@0.11.0':
+ resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
+
+ '@smithy/is-array-buffer@2.2.0':
+ resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==}
+ engines: {node: '>=14.0.0'}
+
+ '@smithy/types@4.13.1':
+ resolution: {integrity: sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==}
+ engines: {node: '>=18.0.0'}
+
+ '@smithy/util-buffer-from@2.2.0':
+ resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==}
+ engines: {node: '>=14.0.0'}
+
+ '@smithy/util-utf8@2.3.0':
+ resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==}
+ engines: {node: '>=14.0.0'}
+
+ '@so-ric/colorspace@1.1.6':
+ resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==}
+
+ '@standard-schema/spec@1.1.0':
+ resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+
+ '@tootallnate/once@1.1.2':
+ resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
+ engines: {node: '>= 6'}
+
+ '@tootallnate/once@2.0.0':
+ resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
+ engines: {node: '>= 10'}
+
+ '@ts-morph/common@0.28.1':
+ resolution: {integrity: sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g==}
+
+ '@tybys/wasm-util@0.10.1':
+ resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
+
+ '@types/caseless@0.12.5':
+ resolution: {integrity: sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==}
+
+ '@types/chai@5.2.3':
+ resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+
+ '@types/deep-eql@4.0.2':
+ resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/geojson@7946.0.16':
+ resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ '@types/lodash@4.17.24':
+ resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==}
+
+ '@types/node@24.12.0':
+ resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==}
+
+ '@types/readable-stream@4.0.23':
+ resolution: {integrity: sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==}
+
+ '@types/request@2.48.13':
+ resolution: {integrity: sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==}
+
+ '@types/retry@0.12.0':
+ resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
+
+ '@types/tough-cookie@4.0.5':
+ resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
+
+ '@types/triple-beam@1.3.5':
+ resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
+
+ '@types/uuid@10.0.0':
+ resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==}
+
+ '@types/ws@8.18.1':
+ resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
+
+ '@typespec/ts-http-runtime@0.3.4':
+ resolution: {integrity: sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@vitest/expect@4.1.2':
+ resolution: {integrity: sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==}
+
+ '@vitest/mocker@4.1.2':
+ resolution: {integrity: sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^6.0.0 || ^7.0.0 || ^8.0.0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+
+ '@vitest/pretty-format@4.1.2':
+ resolution: {integrity: sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==}
+
+ '@vitest/runner@4.1.2':
+ resolution: {integrity: sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==}
+
+ '@vitest/snapshot@4.1.2':
+ resolution: {integrity: sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==}
+
+ '@vitest/spy@4.1.2':
+ resolution: {integrity: sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==}
+
+ '@vitest/utils@4.1.2':
+ resolution: {integrity: sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==}
+
+ abbrev@1.1.1:
+ resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
+
+ abort-controller@3.0.0:
+ resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
+ engines: {node: '>=6.5'}
+
+ accepts@2.0.0:
+ resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
+ engines: {node: '>= 0.6'}
+
+ agent-base@6.0.2:
+ resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
+ engines: {node: '>= 6.0.0'}
+
+ agent-base@7.1.4:
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
+ agentkeepalive@4.6.0:
+ resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
+ engines: {node: '>= 8.0.0'}
+
+ aggregate-error@3.1.0:
+ resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
+ engines: {node: '>=8'}
+
+ ajv-formats@3.0.1:
+ resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+
+ ajv@8.18.0:
+ resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ ansi-styles@5.2.0:
+ resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+ engines: {node: '>=10'}
+
+ aproba@2.1.0:
+ resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==}
+
+ are-we-there-yet@3.0.1:
+ resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==}
+ engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
+ deprecated: This package is no longer supported.
+
+ array-union@2.1.0:
+ resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
+ engines: {node: '>=8'}
+
+ arrify@2.0.1:
+ resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==}
+ engines: {node: '>=8'}
+
+ assertion-error@2.0.1:
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
+
+ async-retry@1.3.3:
+ resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==}
+
+ async@3.2.6:
+ resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
+
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+ aws-ssl-profiles@1.1.2:
+ resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==}
+ engines: {node: '>= 6.0.0'}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ balanced-match@4.0.4:
+ resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
+ engines: {node: 18 || 20 || >=22}
+
+ base64-js@1.5.1:
+ resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
+ bignumber.js@9.3.1:
+ resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
+
+ bindings@1.5.0:
+ resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
+
+ bl@4.1.0:
+ resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
+
+ bl@6.1.6:
+ resolution: {integrity: sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg==}
+
+ body-parser@2.2.2:
+ resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
+ engines: {node: '>=18'}
+
+ brace-expansion@1.1.13:
+ resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==}
+
+ brace-expansion@5.0.5:
+ resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==}
+ engines: {node: 18 || 20 || >=22}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ buffer-equal-constant-time@1.0.1:
+ resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
+
+ buffer@5.7.1:
+ resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
+
+ buffer@6.0.3:
+ resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+
+ bundle-name@4.1.0:
+ resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
+ engines: {node: '>=18'}
+
+ bytes@3.1.2:
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+ engines: {node: '>= 0.8'}
+
+ cacache@15.3.0:
+ resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==}
+ engines: {node: '>= 10'}
+
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
+ call-bound@1.0.4:
+ resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
+ engines: {node: '>= 0.4'}
+
+ camelcase@6.3.0:
+ resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
+ engines: {node: '>=10'}
+
+ chai@6.2.2:
+ resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
+ engines: {node: '>=18'}
+
+ chalk@5.6.2:
+ resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
+ engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+
+ chownr@1.1.4:
+ resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
+
+ chownr@2.0.0:
+ resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
+ engines: {node: '>=10'}
+
+ clean-stack@2.2.0:
+ resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
+ engines: {node: '>=6'}
+
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
+
+ code-block-writer@13.0.3:
+ resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-convert@3.1.3:
+ resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==}
+ engines: {node: '>=14.6'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ color-name@2.1.0:
+ resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==}
+ engines: {node: '>=12.20'}
+
+ color-string@2.1.4:
+ resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==}
+ engines: {node: '>=18'}
+
+ color-support@1.1.3:
+ resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
+ hasBin: true
+
+ color@5.0.3:
+ resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==}
+ engines: {node: '>=18'}
+
+ colorette@2.0.19:
+ resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==}
+
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
+ commander@10.0.1:
+ resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
+ engines: {node: '>=14'}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ console-control-strings@1.1.0:
+ resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
+
+ console-table-printer@2.15.0:
+ resolution: {integrity: sha512-SrhBq4hYVjLCkBVOWaTzceJalvn5K1Zq5aQA6wXC/cYjI3frKWNPEMK3sZsJfNNQApvCQmgBcc13ZKmFj8qExw==}
+
+ content-disposition@1.0.1:
+ resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==}
+ engines: {node: '>=18'}
+
+ content-type@1.0.5:
+ resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
+ engines: {node: '>= 0.6'}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ cookie-signature@1.2.2:
+ resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
+ engines: {node: '>=6.6.0'}
+
+ cookie@0.7.2:
+ resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
+ engines: {node: '>= 0.6'}
+
+ cors@2.8.6:
+ resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
+ engines: {node: '>= 0.10'}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ data-uri-to-buffer@4.0.1:
+ resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
+ engines: {node: '>= 12'}
+
+ dataloader@2.2.3:
+ resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==}
+
+ debug@4.3.4:
+ resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ decamelize@1.2.0:
+ resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
+ engines: {node: '>=0.10.0'}
+
+ decompress-response@6.0.0:
+ resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
+ engines: {node: '>=10'}
+
+ deep-extend@0.6.0:
+ resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
+ engines: {node: '>=4.0.0'}
+
+ deepmerge@4.3.1:
+ resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
+ engines: {node: '>=0.10.0'}
+
+ default-browser-id@5.0.1:
+ resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==}
+ engines: {node: '>=18'}
+
+ default-browser@5.5.0:
+ resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==}
+ engines: {node: '>=18'}
+
+ define-lazy-prop@3.0.0:
+ resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
+ engines: {node: '>=12'}
+
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
+ delegates@1.0.0:
+ resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
+
+ denque@2.1.0:
+ resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
+ engines: {node: '>=0.10'}
+
+ depd@2.0.0:
+ resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+ engines: {node: '>= 0.8'}
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ dir-glob@3.0.1:
+ resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
+ engines: {node: '>=8'}
+
+ dom-serializer@2.0.0:
+ resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
+
+ domelementtype@2.3.0:
+ resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+
+ domhandler@5.0.3:
+ resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
+ engines: {node: '>= 4'}
+
+ domutils@3.2.2:
+ resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
+
+ dotenv@17.3.1:
+ resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==}
+ engines: {node: '>=12'}
+
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
+
+ duplexify@4.1.3:
+ resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==}
+
+ ecdsa-sig-formatter@1.0.11:
+ resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
+
+ ee-first@1.1.1:
+ resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+ enabled@2.0.0:
+ resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
+
+ encodeurl@2.0.0:
+ resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
+ engines: {node: '>= 0.8'}
+
+ encoding@0.1.13:
+ resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
+
+ end-of-stream@1.4.5:
+ resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
+
+ entities@4.5.0:
+ resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+ engines: {node: '>=0.12'}
+
+ env-paths@2.2.1:
+ resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
+ engines: {node: '>=6'}
+
+ err-code@2.0.3:
+ resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
+
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ es-module-lexer@2.0.0:
+ resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==}
+
+ es-object-atoms@1.1.1:
+ resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+ engines: {node: '>= 0.4'}
+
+ es-set-tostringtag@2.1.0:
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ engines: {node: '>= 0.4'}
+
+ esbuild@0.27.4:
+ resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ escape-html@1.0.3:
+ resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+
+ esm@3.2.25:
+ resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==}
+ engines: {node: '>=6'}
+
+ esprima@4.0.1:
+ resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+ etag@1.8.1:
+ resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
+ engines: {node: '>= 0.6'}
+
+ event-target-shim@5.0.1:
+ resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
+ engines: {node: '>=6'}
+
+ eventemitter3@4.0.7:
+ resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
+
+ eventemitter3@5.0.4:
+ resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
+
+ events@3.3.0:
+ resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
+ engines: {node: '>=0.8.x'}
+
+ eventsource-parser@3.0.6:
+ resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
+ engines: {node: '>=18.0.0'}
+
+ eventsource@3.0.7:
+ resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
+ engines: {node: '>=18.0.0'}
+
+ expand-template@2.0.3:
+ resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
+ engines: {node: '>=6'}
+
+ expect-type@1.3.0:
+ resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
+ engines: {node: '>=12.0.0'}
+
+ express-rate-limit@8.3.2:
+ resolution: {integrity: sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==}
+ engines: {node: '>= 16'}
+ peerDependencies:
+ express: '>= 4.11'
+
+ express@5.2.1:
+ resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
+ engines: {node: '>= 18'}
+
+ extend@3.0.2:
+ resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+ engines: {node: '>=8.6.0'}
+
+ fast-uri@3.1.0:
+ resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
+
+ fast-xml-builder@1.1.4:
+ resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==}
+
+ fast-xml-parser@5.5.9:
+ resolution: {integrity: sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g==}
+ hasBin: true
+
+ fastq@1.20.1:
+ resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fecha@4.2.3:
+ resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
+
+ fetch-blob@3.2.0:
+ resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
+ engines: {node: ^12.20 || >= 14.13}
+
+ file-uri-to-path@1.0.0:
+ resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ finalhandler@2.1.1:
+ resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
+ engines: {node: '>= 18.0.0'}
+
+ fn.name@1.1.0:
+ resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
+
+ form-data@2.5.5:
+ resolution: {integrity: sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==}
+ engines: {node: '>= 0.12'}
+
+ formdata-polyfill@4.0.10:
+ resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
+ engines: {node: '>=12.20.0'}
+
+ forwarded@0.2.0:
+ resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
+ engines: {node: '>= 0.6'}
+
+ fresh@2.0.0:
+ resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
+ engines: {node: '>= 0.8'}
+
+ fs-constants@1.0.0:
+ resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
+
+ fs-extra@11.3.3:
+ resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==}
+ engines: {node: '>=14.14'}
+
+ fs-minipass@2.1.0:
+ resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
+ engines: {node: '>= 8'}
+
+ fs.realpath@1.0.0:
+ resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ gauge@4.0.4:
+ resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==}
+ engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
+ deprecated: This package is no longer supported.
+
+ gaxios@6.7.1:
+ resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==}
+ engines: {node: '>=14'}
+
+ gaxios@7.1.4:
+ resolution: {integrity: sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==}
+ engines: {node: '>=18'}
+
+ gcp-metadata@6.1.1:
+ resolution: {integrity: sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==}
+ engines: {node: '>=14'}
+
+ gcp-metadata@8.1.2:
+ resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==}
+ engines: {node: '>=18'}
+
+ generate-function@2.3.1:
+ resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==}
+
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+ engines: {node: '>= 0.4'}
+
+ get-package-type@0.1.0:
+ resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
+ engines: {node: '>=8.0.0'}
+
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+ engines: {node: '>= 0.4'}
+
+ getopts@2.3.0:
+ resolution: {integrity: sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==}
+
+ github-from-package@0.0.0:
+ resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
+
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob@7.2.3:
+ resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
+
+ globby@11.1.0:
+ resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
+ engines: {node: '>=10'}
+
+ google-auth-library@10.6.2:
+ resolution: {integrity: sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==}
+ engines: {node: '>=18'}
+
+ google-auth-library@9.15.1:
+ resolution: {integrity: sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==}
+ engines: {node: '>=14'}
+
+ google-logging-utils@0.0.2:
+ resolution: {integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==}
+ engines: {node: '>=14'}
+
+ google-logging-utils@1.1.3:
+ resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==}
+ engines: {node: '>=14'}
+
+ googleapis-common@7.2.0:
+ resolution: {integrity: sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==}
+ engines: {node: '>=14.0.0'}
+
+ googleapis@137.1.0:
+ resolution: {integrity: sha512-2L7SzN0FLHyQtFmyIxrcXhgust77067pkkduqkbIpDuj9JzVnByxsRrcRfUMFQam3rQkWW2B0f1i40IwKDWIVQ==}
+ engines: {node: '>=14.0.0'}
+
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ gtoken@7.1.0:
+ resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==}
+ engines: {node: '>=14.0.0'}
+
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
+ has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+
+ has-unicode@2.0.1:
+ resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
+ hono@4.12.9:
+ resolution: {integrity: sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==}
+ engines: {node: '>=16.9.0'}
+
+ html-entities@2.6.0:
+ resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==}
+
+ html-to-text@9.0.5:
+ resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==}
+ engines: {node: '>=14'}
+
+ htmlparser2@8.0.2:
+ resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
+
+ http-cache-semantics@4.2.0:
+ resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
+
+ http-errors@2.0.1:
+ resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
+ engines: {node: '>= 0.8'}
+
+ http-proxy-agent@4.0.1:
+ resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==}
+ engines: {node: '>= 6'}
+
+ http-proxy-agent@5.0.0:
+ resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
+ engines: {node: '>= 6'}
+
+ http-proxy-agent@7.0.2:
+ resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
+ engines: {node: '>= 14'}
+
+ https-proxy-agent@5.0.1:
+ resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
+ engines: {node: '>= 6'}
+
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
+ humanize-ms@1.2.1:
+ resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
+
+ iconv-lite@0.6.3:
+ resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+ engines: {node: '>=0.10.0'}
+
+ iconv-lite@0.7.2:
+ resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
+ engines: {node: '>=0.10.0'}
+
+ ieee754@1.2.1:
+ resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ indent-string@4.0.0:
+ resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
+ engines: {node: '>=8'}
+
+ infer-owner@1.0.4:
+ resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==}
+
+ inflight@1.0.6:
+ resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+ deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ ini@1.3.8:
+ resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
+
+ interpret@2.2.0:
+ resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==}
+ engines: {node: '>= 0.10'}
+
+ ip-address@10.1.0:
+ resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==}
+ engines: {node: '>= 12'}
+
+ ipaddr.js@1.9.1:
+ resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
+ engines: {node: '>= 0.10'}
+
+ is-core-module@2.16.1:
+ resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+ engines: {node: '>= 0.4'}
+
+ is-docker@3.0.0:
+ resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ hasBin: true
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-inside-container@1.0.0:
+ resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
+ engines: {node: '>=14.16'}
+ hasBin: true
+
+ is-lambda@1.0.1:
+ resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==}
+
+ is-network-error@1.3.1:
+ resolution: {integrity: sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==}
+ engines: {node: '>=16'}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ is-promise@4.0.0:
+ resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
+
+ is-property@1.0.2:
+ resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==}
+
+ is-stream@2.0.1:
+ resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
+ engines: {node: '>=8'}
+
+ is-wsl@3.1.1:
+ resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==}
+ engines: {node: '>=16'}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ jose@6.2.2:
+ resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==}
+
+ js-md4@0.3.2:
+ resolution: {integrity: sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==}
+
+ js-tiktoken@1.0.21:
+ resolution: {integrity: sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==}
+
+ json-bigint@1.0.0:
+ resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==}
+
+ json-schema-to-ts@3.1.1:
+ resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==}
+ engines: {node: '>=16'}
+
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
+ json-schema-typed@8.0.2:
+ resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
+
+ jsonfile@6.2.0:
+ resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
+
+ jsonwebtoken@9.0.3:
+ resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==}
+ engines: {node: '>=12', npm: '>=6'}
+
+ jwa@2.0.1:
+ resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==}
+
+ jws@4.0.1:
+ resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
+
+ knex@3.2.8:
+ resolution: {integrity: sha512-ElXXxu9Nq+5hWYdBUddYIWIT5yKKs5KNCsmKGbJSHPyaMpAABp3xs4L55GgdQoAs6QQ7dv72ai3M4pxYQ8utEg==}
+ engines: {node: '>=16'}
+ hasBin: true
+ peerDependencies:
+ better-sqlite3: '*'
+ mysql: '*'
+ mysql2: '*'
+ pg: '*'
+ pg-native: '*'
+ pg-query-stream: ^4.14.0
+ sqlite3: '*'
+ tedious: '*'
+ peerDependenciesMeta:
+ better-sqlite3:
+ optional: true
+ mysql:
+ optional: true
+ mysql2:
+ optional: true
+ pg:
+ optional: true
+ pg-native:
+ optional: true
+ pg-query-stream:
+ optional: true
+ sqlite3:
+ optional: true
+ tedious:
+ optional: true
+
+ kuler@2.0.0:
+ resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
+
+ langsmith@0.5.15:
+ resolution: {integrity: sha512-S20JnYmIgqGBjA/WEn12ZZJjqd03O5wd8K9KgGBvsKXQBn0bYuFrr1w20L37PpcMmX3/cftpgJ6g2y8KoEmHLw==}
+ peerDependencies:
+ '@opentelemetry/api': '*'
+ '@opentelemetry/exporter-trace-otlp-proto': '*'
+ '@opentelemetry/sdk-trace-base': '*'
+ openai: '*'
+ ws: '>=7'
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+ '@opentelemetry/exporter-trace-otlp-proto':
+ optional: true
+ '@opentelemetry/sdk-trace-base':
+ optional: true
+ openai:
+ optional: true
+ ws:
+ optional: true
+
+ leac@0.6.0:
+ resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
+
+ lightningcss-android-arm64@1.32.0:
+ resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.32.0:
+ resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.32.0:
+ resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.32.0:
+ resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.32.0:
+ resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.32.0:
+ resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.32.0:
+ resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.32.0:
+ resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.32.0:
+ resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.32.0:
+ resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.32.0:
+ resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.32.0:
+ resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
+ engines: {node: '>= 12.0.0'}
+
+ llamaindex@0.12.1:
+ resolution: {integrity: sha512-/tXXITk/iVGBycOFaDhev6dgTBIr6Ycu4FoPIt6A5JcEAiB6ujONjiV36flVXUR8JdqwMtS767XMjV+36nV4yQ==}
+ engines: {node: '>=18.0.0'}
+
+ lodash-es@4.17.23:
+ resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==}
+
+ lodash.camelcase@4.3.0:
+ resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
+
+ lodash.includes@4.3.0:
+ resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
+
+ lodash.isboolean@3.0.3:
+ resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
+
+ lodash.isinteger@4.0.4:
+ resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
+
+ lodash.isnumber@3.0.3:
+ resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
+
+ lodash.isplainobject@4.0.6:
+ resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
+
+ lodash.isstring@4.0.1:
+ resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
+
+ lodash.once@4.1.1:
+ resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
+
+ lodash@4.17.21:
+ resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+ logform@2.7.0:
+ resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==}
+ engines: {node: '>= 12.0.0'}
+
+ long@5.3.2:
+ resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
+
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
+ lru-cache@6.0.0:
+ resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
+ engines: {node: '>=10'}
+
+ lru.min@1.1.4:
+ resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==}
+ engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'}
+
+ magic-bytes.js@1.13.0:
+ resolution: {integrity: sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==}
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ make-fetch-happen@9.1.0:
+ resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==}
+ engines: {node: '>= 10'}
+
+ mariadb@3.4.5:
+ resolution: {integrity: sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==}
+ engines: {node: '>= 14'}
+
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ engines: {node: '>= 0.4'}
+
+ media-typer@1.1.0:
+ resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
+ engines: {node: '>= 0.8'}
+
+ merge-descriptors@2.0.0:
+ resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
+ engines: {node: '>=18'}
+
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ mikro-orm@6.6.11:
+ resolution: {integrity: sha512-8z1pS5IfKGys0OR0m5bWDLbmCu7n86DXvozL9v7BYcqW6O3GbsioghmNobzl7PraOOIRy260rS+mO6Z1jLduDQ==}
+ engines: {node: '>= 18.12.0'}
+
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-db@1.54.0:
+ resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@3.0.2:
+ resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
+ engines: {node: '>=18'}
+
+ mime@3.0.0:
+ resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
+ engines: {node: '>=10.0.0'}
+ hasBin: true
+
+ mimic-response@3.1.0:
+ resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
+ engines: {node: '>=10'}
+
+ minimatch@10.2.5:
+ resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
+ engines: {node: 18 || 20 || >=22}
+
+ minimatch@3.1.5:
+ resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
+
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+ minipass-collect@1.0.2:
+ resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
+ engines: {node: '>= 8'}
+
+ minipass-fetch@1.4.1:
+ resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==}
+ engines: {node: '>=8'}
+
+ minipass-flush@1.0.7:
+ resolution: {integrity: sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==}
+ engines: {node: '>= 8'}
+
+ minipass-pipeline@1.2.4:
+ resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==}
+ engines: {node: '>=8'}
+
+ minipass-sized@1.0.3:
+ resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==}
+ engines: {node: '>=8'}
+
+ minipass@3.3.6:
+ resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
+ engines: {node: '>=8'}
+
+ minipass@5.0.0:
+ resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
+ engines: {node: '>=8'}
+
+ minizlib@2.1.2:
+ resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
+ engines: {node: '>= 8'}
+
+ mkdirp-classic@0.5.3:
+ resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
+
+ mkdirp@1.0.4:
+ resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ ms@2.1.2:
+ resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ mustache@4.2.0:
+ resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
+ hasBin: true
+
+ mysql2@3.20.0:
+ resolution: {integrity: sha512-eCLUs7BNbgA6nf/MZXsaBO1SfGs0LtLVrJD3WeWq+jPLDWkSufTD+aGMwykfUVPdZnblaUK1a8G/P63cl9FkKg==}
+ engines: {node: '>= 8.0'}
+ peerDependencies:
+ '@types/node': '>= 8'
+
+ named-placeholders@1.1.6:
+ resolution: {integrity: sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==}
+ engines: {node: '>=8.0.0'}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ napi-build-utils@2.0.0:
+ resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
+
+ native-duplexpair@1.0.0:
+ resolution: {integrity: sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA==}
+
+ negotiator@0.6.4:
+ resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==}
+ engines: {node: '>= 0.6'}
+
+ negotiator@1.0.0:
+ resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
+ engines: {node: '>= 0.6'}
+
+ node-abi@3.89.0:
+ resolution: {integrity: sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==}
+ engines: {node: '>=10'}
+
+ node-addon-api@7.1.1:
+ resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
+
+ node-addon-api@8.7.0:
+ resolution: {integrity: sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==}
+ engines: {node: ^18 || ^20 || >= 21}
+
+ node-domexception@1.0.0:
+ resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
+ engines: {node: '>=10.5.0'}
+ deprecated: Use your platform's native DOMException instead
+
+ node-fetch@2.7.0:
+ resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
+ engines: {node: 4.x || >=6.0.0}
+ peerDependencies:
+ encoding: ^0.1.0
+ peerDependenciesMeta:
+ encoding:
+ optional: true
+
+ node-fetch@3.3.2:
+ resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+ node-gyp-build@4.8.4:
+ resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
+ hasBin: true
+
+ node-gyp@8.4.1:
+ resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==}
+ engines: {node: '>= 10.12.0'}
+ hasBin: true
+
+ nopt@5.0.0:
+ resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ npmlog@6.0.2:
+ resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==}
+ engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
+ deprecated: This package is no longer supported.
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-inspect@1.13.4:
+ resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
+ engines: {node: '>= 0.4'}
+
+ obug@2.1.1:
+ resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
+
+ on-finished@2.4.1:
+ resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
+ engines: {node: '>= 0.8'}
+
+ once@1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+ one-time@1.0.0:
+ resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
+
+ open@10.2.0:
+ resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==}
+ engines: {node: '>=18'}
+
+ openai@6.33.0:
+ resolution: {integrity: sha512-xAYN1W3YsDXJWA5F277135YfkEk6H7D3D6vWwRhJ3OEkzRgcyK8z/P5P9Gyi/wB4N8kK9kM5ZjprfvyHagKmpw==}
+ hasBin: true
+ peerDependencies:
+ ws: ^8.18.0
+ zod: ^3.25 || ^4.0
+ peerDependenciesMeta:
+ ws:
+ optional: true
+ zod:
+ optional: true
+
+ p-finally@1.0.0:
+ resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
+ engines: {node: '>=4'}
+
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+
+ p-map@4.0.0:
+ resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
+ engines: {node: '>=10'}
+
+ p-queue@6.6.2:
+ resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==}
+ engines: {node: '>=8'}
+
+ p-queue@9.1.0:
+ resolution: {integrity: sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==}
+ engines: {node: '>=20'}
+
+ p-retry@4.6.2:
+ resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==}
+ engines: {node: '>=8'}
+
+ p-retry@7.1.1:
+ resolution: {integrity: sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==}
+ engines: {node: '>=20'}
+
+ p-timeout@3.2.0:
+ resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==}
+ engines: {node: '>=8'}
+
+ p-timeout@7.0.1:
+ resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==}
+ engines: {node: '>=20'}
+
+ parseley@0.12.1:
+ resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
+
+ parseurl@1.3.3:
+ resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
+ engines: {node: '>= 0.8'}
+
+ path-browserify@1.0.1:
+ resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
+
+ path-expression-matcher@1.2.0:
+ resolution: {integrity: sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==}
+ engines: {node: '>=14.0.0'}
+
+ path-is-absolute@1.0.1:
+ resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+ engines: {node: '>=0.10.0'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ path-to-regexp@8.4.1:
+ resolution: {integrity: sha512-fvU78fIjZ+SBM9YwCknCvKOUKkLVqtWDVctl0s7xIqfmfb38t2TT4ZU2gHm+Z8xGwgW+QWEU3oQSAzIbo89Ggw==}
+
+ path-type@4.0.0:
+ resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+ engines: {node: '>=8'}
+
+ pathe@1.1.2:
+ resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
+
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+ peberminta@0.9.0:
+ resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
+
+ pg-cloudflare@1.3.0:
+ resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==}
+
+ pg-connection-string@2.12.0:
+ resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==}
+
+ pg-connection-string@2.6.2:
+ resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==}
+
+ pg-int8@1.0.1:
+ resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
+ engines: {node: '>=4.0.0'}
+
+ pg-pool@3.13.0:
+ resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==}
+ peerDependencies:
+ pg: '>=8.0'
+
+ pg-protocol@1.13.0:
+ resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==}
+
+ pg-types@2.2.0:
+ resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
+ engines: {node: '>=4'}
+
+ pg@8.20.0:
+ resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==}
+ engines: {node: '>= 16.0.0'}
+ peerDependencies:
+ pg-native: '>=3.0.1'
+ peerDependenciesMeta:
+ pg-native:
+ optional: true
+
+ pgpass@1.0.5:
+ resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@2.3.2:
+ resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
+ engines: {node: '>=8.6'}
+
+ picomatch@4.0.4:
+ resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
+ engines: {node: '>=12'}
+
+ pkce-challenge@5.0.1:
+ resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
+ engines: {node: '>=16.20.0'}
+
+ postcss@8.5.8:
+ resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ postgres-array@2.0.0:
+ resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
+ engines: {node: '>=4'}
+
+ postgres-array@3.0.4:
+ resolution: {integrity: sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==}
+ engines: {node: '>=12'}
+
+ postgres-bytea@1.0.1:
+ resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==}
+ engines: {node: '>=0.10.0'}
+
+ postgres-date@1.0.7:
+ resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
+ engines: {node: '>=0.10.0'}
+
+ postgres-date@2.1.0:
+ resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==}
+ engines: {node: '>=12'}
+
+ postgres-interval@1.2.0:
+ resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
+ engines: {node: '>=0.10.0'}
+
+ postgres-interval@4.0.2:
+ resolution: {integrity: sha512-EMsphSQ1YkQqKZL2cuG0zHkmjCCzQqQ71l2GXITqRwjhRleCdv00bDk/ktaSi0LnlaPzAc3535KTrjXsTdtx7A==}
+ engines: {node: '>=12'}
+
+ prebuild-install@7.1.3:
+ resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
+ engines: {node: '>=10'}
+ deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available.
+ hasBin: true
+
+ process@0.11.10:
+ resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
+ engines: {node: '>= 0.6.0'}
+
+ promise-inflight@1.0.1:
+ resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}
+ peerDependencies:
+ bluebird: '*'
+ peerDependenciesMeta:
+ bluebird:
+ optional: true
+
+ promise-retry@2.0.1:
+ resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
+ engines: {node: '>=10'}
+
+ protobufjs@7.5.4:
+ resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==}
+ engines: {node: '>=12.0.0'}
+
+ proxy-addr@2.0.7:
+ resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
+ engines: {node: '>= 0.10'}
+
+ pump@3.0.4:
+ resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
+
+ qs@6.15.0:
+ resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==}
+ engines: {node: '>=0.6'}
+
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ range-parser@1.2.1:
+ resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
+ engines: {node: '>= 0.6'}
+
+ raw-body@3.0.2:
+ resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
+ engines: {node: '>= 0.10'}
+
+ rc@1.2.8:
+ resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
+ hasBin: true
+
+ readable-stream@3.6.2:
+ resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
+ engines: {node: '>= 6'}
+
+ readable-stream@4.7.0:
+ resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ rechoir@0.8.0:
+ resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==}
+ engines: {node: '>= 10.13.0'}
+
+ reflect-metadata@0.2.2:
+ resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
+
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
+ require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+
+ resolve-from@5.0.0:
+ resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
+ engines: {node: '>=8'}
+
+ resolve@1.22.11:
+ resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ retry-request@7.0.2:
+ resolution: {integrity: sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==}
+ engines: {node: '>=14'}
+
+ retry@0.12.0:
+ resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
+ engines: {node: '>= 4'}
+
+ retry@0.13.1:
+ resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
+ engines: {node: '>= 4'}
+
+ reusify@1.1.0:
+ resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+ rimraf@3.0.2:
+ resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+ deprecated: Rimraf versions prior to v4 are no longer supported
+ hasBin: true
+
+ rolldown@1.0.0-rc.12:
+ resolution: {integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+
+ router@2.2.0:
+ resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
+ engines: {node: '>= 18'}
+
+ run-applescript@7.1.0:
+ resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
+ engines: {node: '>=18'}
+
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+ safe-stable-stringify@2.5.0:
+ resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
+ engines: {node: '>=10'}
+
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+ selderee@0.11.0:
+ resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==}
+
+ semver@7.7.4:
+ resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ send@1.2.1:
+ resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
+ engines: {node: '>= 18'}
+
+ serve-static@2.2.1:
+ resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
+ engines: {node: '>= 18'}
+
+ set-blocking@2.0.0:
+ resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
+
+ setprototypeof@1.2.0:
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ side-channel-list@1.0.0:
+ resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-map@1.0.1:
+ resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-weakmap@1.0.2:
+ resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
+ engines: {node: '>= 0.4'}
+
+ side-channel@1.1.0:
+ resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
+ engines: {node: '>= 0.4'}
+
+ siginfo@2.0.0:
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+
+ signal-exit@3.0.7:
+ resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+
+ simple-concat@1.0.1:
+ resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
+
+ simple-get@4.0.1:
+ resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
+
+ simple-wcswidth@1.1.2:
+ resolution: {integrity: sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==}
+
+ slash@3.0.0:
+ resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
+ engines: {node: '>=8'}
+
+ smart-buffer@4.2.0:
+ resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
+ engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
+
+ socks-proxy-agent@6.2.1:
+ resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==}
+ engines: {node: '>= 10'}
+
+ socks@2.8.7:
+ resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==}
+ engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ split2@4.2.0:
+ resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
+ engines: {node: '>= 10.x'}
+
+ sprintf-js@1.1.3:
+ resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
+
+ sql-escaper@1.3.3:
+ resolution: {integrity: sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==}
+ engines: {bun: '>=1.0.0', deno: '>=2.0.0', node: '>=12.0.0'}
+
+ sqlite3@5.1.7:
+ resolution: {integrity: sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==}
+
+ sqlstring-sqlite@0.1.1:
+ resolution: {integrity: sha512-9CAYUJ0lEUPYJrswqiqdINNSfq3jqWo/bFJ7tufdoNeSK0Fy+d1kFTxjqO9PIqza0Kri+ZtYMfPVf1aZaFOvrQ==}
+ engines: {node: '>= 0.6'}
+
+ sqlstring@2.3.3:
+ resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
+ engines: {node: '>= 0.6'}
+
+ ssri@8.0.1:
+ resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==}
+ engines: {node: '>= 8'}
+
+ stack-trace@0.0.10:
+ resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
+
+ stackback@0.0.2:
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+
+ statuses@2.0.2:
+ resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
+ engines: {node: '>= 0.8'}
+
+ std-env@4.0.0:
+ resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==}
+
+ stream-events@1.0.5:
+ resolution: {integrity: sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==}
+
+ stream-shift@1.0.3:
+ resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
+
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string_decoder@1.3.0:
+ resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-json-comments@2.0.1:
+ resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
+ engines: {node: '>=0.10.0'}
+
+ strnum@2.2.2:
+ resolution: {integrity: sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==}
+
+ stubs@3.0.0:
+ resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==}
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ tar-fs@2.1.4:
+ resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==}
+
+ tar-stream@2.2.0:
+ resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
+ engines: {node: '>=6'}
+
+ tar@6.2.1:
+ resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
+ engines: {node: '>=10'}
+ deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
+
+ tarn@3.0.2:
+ resolution: {integrity: sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==}
+ engines: {node: '>=8.0.0'}
+
+ tedious@19.2.1:
+ resolution: {integrity: sha512-pk1Q16Yl62iocuQB+RWbg6rFUFkIyzqOFQ6NfysCltRvQqKwfurgj8v/f2X+CKvDhSL4IJ0cCOfCHDg9PWEEYA==}
+ engines: {node: '>=18.17'}
+
+ teeny-request@9.0.0:
+ resolution: {integrity: sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==}
+ engines: {node: '>=14'}
+
+ text-hex@1.0.0:
+ resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
+
+ tildify@2.0.0:
+ resolution: {integrity: sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==}
+ engines: {node: '>=8'}
+
+ tinybench@2.9.0:
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
+ tinyexec@1.0.4:
+ resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==}
+ engines: {node: '>=18'}
+
+ tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+
+ tinyrainbow@3.1.0:
+ resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
+ engines: {node: '>=14.0.0'}
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ toidentifier@1.0.1:
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+ engines: {node: '>=0.6'}
+
+ tr46@0.0.3:
+ resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+
+ tree-sitter@0.22.4:
+ resolution: {integrity: sha512-usbHZP9/oxNsUY65MQUsduGRqDHQOou1cagUSwjhoSYAmSahjQDAVsh9s+SlZkn8X8+O1FULRGwHu7AFP3kjzg==}
+
+ triple-beam@1.4.1:
+ resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
+ engines: {node: '>= 14.0.0'}
+
+ ts-algebra@2.0.0:
+ resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==}
+
+ ts-morph@27.0.2:
+ resolution: {integrity: sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w==}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ tsqlstring@1.0.1:
+ resolution: {integrity: sha512-6Nzj/SrVg1SF+egwP4OMAgEa83nLKXIE3EHn+6YKinMUeMj8bGIeLuDCkDC3Cc4OIM+xhw4CD0oXKxal8J/Y6A==}
+ engines: {node: '>= 8.0'}
+
+ tunnel-agent@0.6.0:
+ resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
+
+ type-is@2.0.1:
+ resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
+ engines: {node: '>= 0.6'}
+
+ typescript@6.0.2:
+ resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ undici-types@7.16.0:
+ resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
+
+ unique-filename@1.1.1:
+ resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==}
+
+ unique-slug@2.0.2:
+ resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==}
+
+ universalify@2.0.1:
+ resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
+ engines: {node: '>= 10.0.0'}
+
+ unpipe@1.0.0:
+ resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
+ engines: {node: '>= 0.8'}
+
+ url-template@2.0.8:
+ resolution: {integrity: sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==}
+
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+ uuid@10.0.0:
+ resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
+ hasBin: true
+
+ uuid@11.1.0:
+ resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
+ hasBin: true
+
+ uuid@13.0.0:
+ resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==}
+ hasBin: true
+
+ uuid@8.3.2:
+ resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
+ hasBin: true
+
+ uuid@9.0.1:
+ resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
+ hasBin: true
+
+ vary@1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+
+ vite@8.0.3:
+ resolution: {integrity: sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^20.19.0 || >=22.12.0
+ '@vitejs/devtools': ^0.1.0
+ esbuild: ^0.27.0
+ jiti: '>=1.21.0'
+ less: ^4.0.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: '>=0.54.8'
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ '@vitejs/devtools':
+ optional: true
+ esbuild:
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ vitest@4.1.2:
+ resolution: {integrity: sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==}
+ engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@opentelemetry/api': ^1.9.0
+ '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
+ '@vitest/browser-playwright': 4.1.2
+ '@vitest/browser-preview': 4.1.2
+ '@vitest/browser-webdriverio': 4.1.2
+ '@vitest/ui': 4.1.2
+ happy-dom: '*'
+ jsdom: '*'
+ vite: ^6.0.0 || ^7.0.0 || ^8.0.0
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@opentelemetry/api':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser-playwright':
+ optional: true
+ '@vitest/browser-preview':
+ optional: true
+ '@vitest/browser-webdriverio':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+
+ web-streams-polyfill@3.3.3:
+ resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
+ engines: {node: '>= 8'}
+
+ web-tree-sitter@0.24.7:
+ resolution: {integrity: sha512-CdC/TqVFbXqR+C51v38hv6wOPatKEUGxa39scAeFSm98wIhZxAYonhRQPSMmfZ2w7JDI0zQDdzdmgtNk06/krQ==}
+
+ webidl-conversions@3.0.1:
+ resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+
+ whatwg-url@5.0.0:
+ resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ why-is-node-running@2.3.0:
+ resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
+ engines: {node: '>=8'}
+ hasBin: true
+
+ wide-align@1.1.5:
+ resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
+
+ winston-transport@4.9.0:
+ resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==}
+ engines: {node: '>= 12.0.0'}
+
+ winston@3.19.0:
+ resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==}
+ engines: {node: '>= 12.0.0'}
+
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
+ wrappy@1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+ ws@8.20.0:
+ resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ wsl-utils@0.1.0:
+ resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==}
+ engines: {node: '>=18'}
+
+ xtend@4.0.2:
+ resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
+ engines: {node: '>=0.4'}
+
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
+ yallist@4.0.0:
+ resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yargs@17.7.2:
+ resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+ engines: {node: '>=12'}
+
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
+ zod-to-json-schema@3.25.2:
+ resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==}
+ peerDependencies:
+ zod: ^3.25.28 || ^4
+
+ zod@4.1.8:
+ resolution: {integrity: sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==}
+
+ zod@4.3.6:
+ resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
+
+snapshots:
+
+ '@a2a-js/sdk@0.3.13(@grpc/grpc-js@1.14.3)(express@5.2.1)':
+ dependencies:
+ uuid: 11.1.0
+ optionalDependencies:
+ '@grpc/grpc-js': 1.14.3
+ express: 5.2.1
+
+ '@anthropic-ai/sdk@0.80.0(zod@4.3.6)':
+ dependencies:
+ json-schema-to-ts: 3.1.1
+ optionalDependencies:
+ zod: 4.3.6
+
+ '@aws-crypto/sha256-js@5.2.0':
+ dependencies:
+ '@aws-crypto/util': 5.2.0
+ '@aws-sdk/types': 3.973.6
+ tslib: 2.8.1
+
+ '@aws-crypto/util@5.2.0':
+ dependencies:
+ '@aws-sdk/types': 3.973.6
+ '@smithy/util-utf8': 2.3.0
+ tslib: 2.8.1
+
+ '@aws-sdk/types@3.973.6':
+ dependencies:
+ '@smithy/types': 4.13.1
+ tslib: 2.8.1
+
+ '@azure-rest/core-client@2.5.1':
+ dependencies:
+ '@azure/abort-controller': 2.1.2
+ '@azure/core-auth': 1.10.1
+ '@azure/core-rest-pipeline': 1.23.0
+ '@azure/core-tracing': 1.3.1
+ '@typespec/ts-http-runtime': 0.3.4
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@azure/abort-controller@2.1.2':
+ dependencies:
+ tslib: 2.8.1
+
+ '@azure/core-auth@1.10.1':
+ dependencies:
+ '@azure/abort-controller': 2.1.2
+ '@azure/core-util': 1.13.1
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@azure/core-client@1.10.1':
+ dependencies:
+ '@azure/abort-controller': 2.1.2
+ '@azure/core-auth': 1.10.1
+ '@azure/core-rest-pipeline': 1.23.0
+ '@azure/core-tracing': 1.3.1
+ '@azure/core-util': 1.13.1
+ '@azure/logger': 1.3.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@azure/core-http-compat@2.3.2(@azure/core-client@1.10.1)(@azure/core-rest-pipeline@1.23.0)':
+ dependencies:
+ '@azure/abort-controller': 2.1.2
+ '@azure/core-client': 1.10.1
+ '@azure/core-rest-pipeline': 1.23.0
+
+ '@azure/core-lro@2.7.2':
+ dependencies:
+ '@azure/abort-controller': 2.1.2
+ '@azure/core-util': 1.13.1
+ '@azure/logger': 1.3.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@azure/core-paging@1.6.2':
+ dependencies:
+ tslib: 2.8.1
+
+ '@azure/core-rest-pipeline@1.23.0':
+ dependencies:
+ '@azure/abort-controller': 2.1.2
+ '@azure/core-auth': 1.10.1
+ '@azure/core-tracing': 1.3.1
+ '@azure/core-util': 1.13.1
+ '@azure/logger': 1.3.0
+ '@typespec/ts-http-runtime': 0.3.4
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@azure/core-tracing@1.3.1':
+ dependencies:
+ tslib: 2.8.1
+
+ '@azure/core-util@1.13.1':
+ dependencies:
+ '@azure/abort-controller': 2.1.2
+ '@typespec/ts-http-runtime': 0.3.4
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@azure/identity@4.13.1':
+ dependencies:
+ '@azure/abort-controller': 2.1.2
+ '@azure/core-auth': 1.10.1
+ '@azure/core-client': 1.10.1
+ '@azure/core-rest-pipeline': 1.23.0
+ '@azure/core-tracing': 1.3.1
+ '@azure/core-util': 1.13.1
+ '@azure/logger': 1.3.0
+ '@azure/msal-browser': 5.6.2
+ '@azure/msal-node': 5.1.1
+ open: 10.2.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@azure/keyvault-common@2.0.0':
+ dependencies:
+ '@azure/abort-controller': 2.1.2
+ '@azure/core-auth': 1.10.1
+ '@azure/core-client': 1.10.1
+ '@azure/core-rest-pipeline': 1.23.0
+ '@azure/core-tracing': 1.3.1
+ '@azure/core-util': 1.13.1
+ '@azure/logger': 1.3.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@azure/keyvault-keys@4.10.0(@azure/core-client@1.10.1)':
+ dependencies:
+ '@azure-rest/core-client': 2.5.1
+ '@azure/abort-controller': 2.1.2
+ '@azure/core-auth': 1.10.1
+ '@azure/core-http-compat': 2.3.2(@azure/core-client@1.10.1)(@azure/core-rest-pipeline@1.23.0)
+ '@azure/core-lro': 2.7.2
+ '@azure/core-paging': 1.6.2
+ '@azure/core-rest-pipeline': 1.23.0
+ '@azure/core-tracing': 1.3.1
+ '@azure/core-util': 1.13.1
+ '@azure/keyvault-common': 2.0.0
+ '@azure/logger': 1.3.0
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - '@azure/core-client'
+ - supports-color
+
+ '@azure/logger@1.3.0':
+ dependencies:
+ '@typespec/ts-http-runtime': 0.3.4
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@azure/msal-browser@5.6.2':
+ dependencies:
+ '@azure/msal-common': 16.4.0
+
+ '@azure/msal-common@16.4.0': {}
+
+ '@azure/msal-node@5.1.1':
+ dependencies:
+ '@azure/msal-common': 16.4.0
+ jsonwebtoken: 9.0.3
+ uuid: 8.3.2
+
+ '@babel/runtime@7.29.2': {}
+
+ '@cfworker/json-schema@4.1.1': {}
+
+ '@colors/colors@1.6.0': {}
+
+ '@dabh/diagnostics@2.0.8':
+ dependencies:
+ '@so-ric/colorspace': 1.1.6
+ enabled: 2.0.0
+ kuler: 2.0.0
+
+ '@emnapi/core@1.9.1':
+ dependencies:
+ '@emnapi/wasi-threads': 1.2.0
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/runtime@1.9.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/wasi-threads@1.2.0':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@esbuild/aix-ppc64@0.27.4':
+ optional: true
+
+ '@esbuild/android-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/android-arm@0.27.4':
+ optional: true
+
+ '@esbuild/android-x64@0.27.4':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/darwin-x64@0.27.4':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.27.4':
+ optional: true
+
+ '@esbuild/linux-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/linux-arm@0.27.4':
+ optional: true
+
+ '@esbuild/linux-ia32@0.27.4':
+ optional: true
+
+ '@esbuild/linux-loong64@0.27.4':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.27.4':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.27.4':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.27.4':
+ optional: true
+
+ '@esbuild/linux-s390x@0.27.4':
+ optional: true
+
+ '@esbuild/linux-x64@0.27.4':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.27.4':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.27.4':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/sunos-x64@0.27.4':
+ optional: true
+
+ '@esbuild/win32-arm64@0.27.4':
+ optional: true
+
+ '@esbuild/win32-ia32@0.27.4':
+ optional: true
+
+ '@esbuild/win32-x64@0.27.4':
+ optional: true
+
+ '@finom/zod-to-json-schema@3.24.11(zod@4.3.6)':
+ dependencies:
+ zod: 4.3.6
+
+ '@gar/promisify@1.1.3':
+ optional: true
+
+ '@google-cloud/opentelemetry-cloud-monitoring-exporter@0.21.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.6.1(@opentelemetry/api@1.9.1))(encoding@0.1.13)':
+ dependencies:
+ '@google-cloud/opentelemetry-resource-util': 3.0.0(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.6.1(@opentelemetry/api@1.9.1))(encoding@0.1.13)
+ '@google-cloud/precise-date': 4.0.0
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-metrics': 2.6.1(@opentelemetry/api@1.9.1)
+ google-auth-library: 9.15.1(encoding@0.1.13)
+ googleapis: 137.1.0(encoding@0.1.13)
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ '@google-cloud/opentelemetry-cloud-trace-exporter@3.0.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(encoding@0.1.13)':
+ dependencies:
+ '@google-cloud/opentelemetry-resource-util': 3.0.0(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.6.1(@opentelemetry/api@1.9.1))(encoding@0.1.13)
+ '@grpc/grpc-js': 1.14.3
+ '@grpc/proto-loader': 0.8.0
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-trace-base': 2.6.1(@opentelemetry/api@1.9.1)
+ google-auth-library: 9.15.1(encoding@0.1.13)
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ '@google-cloud/opentelemetry-resource-util@3.0.0(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.6.1(@opentelemetry/api@1.9.1))(encoding@0.1.13)':
+ dependencies:
+ '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/semantic-conventions': 1.40.0
+ gcp-metadata: 6.1.1(encoding@0.1.13)
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ '@google-cloud/paginator@5.0.2':
+ dependencies:
+ arrify: 2.0.1
+ extend: 3.0.2
+
+ '@google-cloud/precise-date@4.0.0': {}
+
+ '@google-cloud/projectify@4.0.0': {}
+
+ '@google-cloud/promisify@4.0.0': {}
+
+ '@google-cloud/storage@7.19.0(encoding@0.1.13)':
+ dependencies:
+ '@google-cloud/paginator': 5.0.2
+ '@google-cloud/projectify': 4.0.0
+ '@google-cloud/promisify': 4.0.0
+ abort-controller: 3.0.0
+ async-retry: 1.3.3
+ duplexify: 4.1.3
+ fast-xml-parser: 5.5.9
+ gaxios: 6.7.1(encoding@0.1.13)
+ google-auth-library: 9.15.1(encoding@0.1.13)
+ html-entities: 2.6.0
+ mime: 3.0.0
+ p-limit: 3.1.0
+ retry-request: 7.0.2(encoding@0.1.13)
+ teeny-request: 9.0.0(encoding@0.1.13)
+ uuid: 8.3.2
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ '@google/adk@0.5.0(ee6095569807c0f2faf9175cb5eca775)':
+ dependencies:
+ '@a2a-js/sdk': 0.3.13(@grpc/grpc-js@1.14.3)(express@5.2.1)
+ '@google-cloud/opentelemetry-cloud-monitoring-exporter': 0.21.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-metrics@2.6.1(@opentelemetry/api@1.9.1))(encoding@0.1.13)
+ '@google-cloud/opentelemetry-cloud-trace-exporter': 3.0.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/resources@2.6.1(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(encoding@0.1.13)
+ '@google-cloud/storage': 7.19.0(encoding@0.1.13)
+ '@google/genai': 1.47.0(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))
+ '@mikro-orm/core': 6.6.11
+ '@mikro-orm/mariadb': 6.6.11(@mikro-orm/core@6.6.11)(pg@8.20.0)
+ '@mikro-orm/mssql': 6.6.11(@azure/core-client@1.10.1)(@mikro-orm/core@6.6.11)(mariadb@3.4.5)(pg@8.20.0)
+ '@mikro-orm/mysql': 6.6.11(@mikro-orm/core@6.6.11)(@types/node@24.12.0)(mariadb@3.4.5)(pg@8.20.0)
+ '@mikro-orm/postgresql': 6.6.11(@mikro-orm/core@6.6.11)(mariadb@3.4.5)
+ '@mikro-orm/reflection': 6.6.11(@mikro-orm/core@6.6.11)
+ '@mikro-orm/sqlite': 6.6.11(@mikro-orm/core@6.6.11)(mariadb@3.4.5)(pg@8.20.0)
+ '@modelcontextprotocol/sdk': 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/api-logs': 0.213.0
+ '@opentelemetry/exporter-logs-otlp-http': 0.205.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/exporter-metrics-otlp-http': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/exporter-trace-otlp-http': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resource-detector-gcp': 0.40.3(@opentelemetry/api@1.9.1)(encoding@0.1.13)
+ '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-logs': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-metrics': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-trace-base': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-trace-node': 2.6.1(@opentelemetry/api@1.9.1)
+ google-auth-library: 10.6.2
+ lodash-es: 4.17.23
+ winston: 3.19.0
+ zod: 4.3.6
+ zod-to-json-schema: 3.25.2(zod@4.3.6)
+ transitivePeerDependencies:
+ - '@bufbuild/protobuf'
+ - '@cfworker/json-schema'
+ - '@grpc/grpc-js'
+ - bufferutil
+ - express
+ - supports-color
+ - utf-8-validate
+
+ '@google/genai@1.47.0(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))':
+ dependencies:
+ google-auth-library: 10.6.2
+ p-retry: 4.6.2
+ protobufjs: 7.5.4
+ ws: 8.20.0
+ optionalDependencies:
+ '@modelcontextprotocol/sdk': 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ '@grpc/grpc-js@1.14.3':
+ dependencies:
+ '@grpc/proto-loader': 0.8.0
+ '@js-sdsl/ordered-map': 4.4.2
+
+ '@grpc/proto-loader@0.8.0':
+ dependencies:
+ lodash.camelcase: 4.3.0
+ long: 5.3.2
+ protobufjs: 7.5.4
+ yargs: 17.7.2
+
+ '@hono/node-server@1.19.12(hono@4.12.9)':
+ dependencies:
+ hono: 4.12.9
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@js-joda/core@5.7.0': {}
+
+ '@js-sdsl/ordered-map@4.4.2': {}
+
+ '@langchain/core@1.1.38(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(openai@6.33.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0)':
+ dependencies:
+ '@cfworker/json-schema': 4.1.1
+ '@standard-schema/spec': 1.1.0
+ ansi-styles: 5.2.0
+ camelcase: 6.3.0
+ decamelize: 1.2.0
+ js-tiktoken: 1.0.21
+ langsmith: 0.5.15(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(openai@6.33.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0)
+ mustache: 4.2.0
+ p-queue: 6.6.2
+ uuid: 11.1.0
+ zod: 4.3.6
+ transitivePeerDependencies:
+ - '@opentelemetry/api'
+ - '@opentelemetry/exporter-trace-otlp-proto'
+ - '@opentelemetry/sdk-trace-base'
+ - openai
+ - ws
+
+ '@langchain/langgraph-checkpoint@1.0.1(@langchain/core@1.1.38(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(openai@6.33.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0))':
+ dependencies:
+ '@langchain/core': 1.1.38(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(openai@6.33.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0)
+ uuid: 10.0.0
+
+ '@langchain/langgraph-sdk@1.8.3(@langchain/core@1.1.38(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(openai@6.33.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0))':
+ dependencies:
+ '@types/json-schema': 7.0.15
+ p-queue: 9.1.0
+ p-retry: 7.1.1
+ uuid: 13.0.0
+ optionalDependencies:
+ '@langchain/core': 1.1.38(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(openai@6.33.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0)
+
+ '@langchain/langgraph@1.2.6(@langchain/core@1.1.38(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(openai@6.33.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0))(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6)':
+ dependencies:
+ '@langchain/core': 1.1.38(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(openai@6.33.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0)
+ '@langchain/langgraph-checkpoint': 1.0.1(@langchain/core@1.1.38(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(openai@6.33.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0))
+ '@langchain/langgraph-sdk': 1.8.3(@langchain/core@1.1.38(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(openai@6.33.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0))
+ '@standard-schema/spec': 1.1.0
+ uuid: 10.0.0
+ zod: 4.3.6
+ optionalDependencies:
+ zod-to-json-schema: 3.25.2(zod@4.3.6)
+ transitivePeerDependencies:
+ - react
+ - react-dom
+ - svelte
+ - vue
+
+ '@llamaindex/core@0.6.22':
+ dependencies:
+ '@finom/zod-to-json-schema': 3.24.11(zod@4.3.6)
+ '@llamaindex/env': 0.1.30
+ '@types/node': 24.12.0
+ magic-bytes.js: 1.13.0
+ zod: 4.3.6
+ transitivePeerDependencies:
+ - '@huggingface/transformers'
+ - gpt-tokenizer
+
+ '@llamaindex/env@0.1.30':
+ dependencies:
+ '@aws-crypto/sha256-js': 5.2.0
+ js-tiktoken: 1.0.21
+ pathe: 1.1.2
+
+ '@llamaindex/node-parser@2.0.22(@llamaindex/core@0.6.22)(@llamaindex/env@0.1.30)(tree-sitter@0.22.4)(web-tree-sitter@0.24.7)':
+ dependencies:
+ '@llamaindex/core': 0.6.22
+ '@llamaindex/env': 0.1.30
+ html-to-text: 9.0.5
+ tree-sitter: 0.22.4
+ web-tree-sitter: 0.24.7
+
+ '@llamaindex/workflow-core@1.3.4(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(hono@4.12.9)(zod@4.3.6)':
+ optionalDependencies:
+ '@modelcontextprotocol/sdk': 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)
+ hono: 4.12.9
+ zod: 4.3.6
+
+ '@llamaindex/workflow@1.1.24(@llamaindex/core@0.6.22)(@llamaindex/env@0.1.30)(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(hono@4.12.9)(zod@4.3.6)':
+ dependencies:
+ '@llamaindex/core': 0.6.22
+ '@llamaindex/env': 0.1.30
+ '@llamaindex/workflow-core': 1.3.4(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(hono@4.12.9)(zod@4.3.6)
+ transitivePeerDependencies:
+ - '@modelcontextprotocol/sdk'
+ - hono
+ - next
+ - p-retry
+ - rxjs
+ - zod
+
+ '@mikro-orm/core@6.6.11':
+ dependencies:
+ dataloader: 2.2.3
+ dotenv: 17.3.1
+ esprima: 4.0.1
+ fs-extra: 11.3.3
+ globby: 11.1.0
+ mikro-orm: 6.6.11
+ reflect-metadata: 0.2.2
+
+ '@mikro-orm/knex@6.6.11(@mikro-orm/core@6.6.11)(mariadb@3.4.5)(mysql2@3.20.0(@types/node@24.12.0))(pg@8.20.0)':
+ dependencies:
+ '@mikro-orm/core': 6.6.11
+ fs-extra: 11.3.3
+ knex: 3.2.8(mysql2@3.20.0(@types/node@24.12.0))(pg@8.20.0)
+ sqlstring: 2.3.3
+ optionalDependencies:
+ mariadb: 3.4.5
+ transitivePeerDependencies:
+ - mysql
+ - mysql2
+ - pg
+ - pg-native
+ - pg-query-stream
+ - sqlite3
+ - supports-color
+ - tedious
+
+ '@mikro-orm/knex@6.6.11(@mikro-orm/core@6.6.11)(mariadb@3.4.5)(pg@8.20.0)':
+ dependencies:
+ '@mikro-orm/core': 6.6.11
+ fs-extra: 11.3.3
+ knex: 3.2.8(pg@8.20.0)
+ sqlstring: 2.3.3
+ optionalDependencies:
+ mariadb: 3.4.5
+ transitivePeerDependencies:
+ - mysql
+ - mysql2
+ - pg
+ - pg-native
+ - pg-query-stream
+ - sqlite3
+ - supports-color
+ - tedious
+
+ '@mikro-orm/knex@6.6.11(@mikro-orm/core@6.6.11)(mariadb@3.4.5)(pg@8.20.0)(sqlite3@5.1.7)':
+ dependencies:
+ '@mikro-orm/core': 6.6.11
+ fs-extra: 11.3.3
+ knex: 3.2.8(pg@8.20.0)(sqlite3@5.1.7)
+ sqlstring: 2.3.3
+ optionalDependencies:
+ mariadb: 3.4.5
+ transitivePeerDependencies:
+ - mysql
+ - mysql2
+ - pg
+ - pg-native
+ - pg-query-stream
+ - sqlite3
+ - supports-color
+ - tedious
+
+ '@mikro-orm/knex@6.6.11(@mikro-orm/core@6.6.11)(mariadb@3.4.5)(pg@8.20.0)(tedious@19.2.1(@azure/core-client@1.10.1))':
+ dependencies:
+ '@mikro-orm/core': 6.6.11
+ fs-extra: 11.3.3
+ knex: 3.2.8(pg@8.20.0)(tedious@19.2.1(@azure/core-client@1.10.1))
+ sqlstring: 2.3.3
+ optionalDependencies:
+ mariadb: 3.4.5
+ transitivePeerDependencies:
+ - mysql
+ - mysql2
+ - pg
+ - pg-native
+ - pg-query-stream
+ - sqlite3
+ - supports-color
+ - tedious
+
+ '@mikro-orm/mariadb@6.6.11(@mikro-orm/core@6.6.11)(pg@8.20.0)':
+ dependencies:
+ '@mikro-orm/core': 6.6.11
+ '@mikro-orm/knex': 6.6.11(@mikro-orm/core@6.6.11)(mariadb@3.4.5)(pg@8.20.0)
+ mariadb: 3.4.5
+ transitivePeerDependencies:
+ - better-sqlite3
+ - libsql
+ - mysql
+ - mysql2
+ - pg
+ - pg-native
+ - pg-query-stream
+ - sqlite3
+ - supports-color
+ - tedious
+
+ '@mikro-orm/mssql@6.6.11(@azure/core-client@1.10.1)(@mikro-orm/core@6.6.11)(mariadb@3.4.5)(pg@8.20.0)':
+ dependencies:
+ '@mikro-orm/core': 6.6.11
+ '@mikro-orm/knex': 6.6.11(@mikro-orm/core@6.6.11)(mariadb@3.4.5)(pg@8.20.0)(tedious@19.2.1(@azure/core-client@1.10.1))
+ tedious: 19.2.1(@azure/core-client@1.10.1)
+ tsqlstring: 1.0.1
+ transitivePeerDependencies:
+ - '@azure/core-client'
+ - better-sqlite3
+ - libsql
+ - mariadb
+ - mysql
+ - mysql2
+ - pg
+ - pg-native
+ - pg-query-stream
+ - sqlite3
+ - supports-color
+
+ '@mikro-orm/mysql@6.6.11(@mikro-orm/core@6.6.11)(@types/node@24.12.0)(mariadb@3.4.5)(pg@8.20.0)':
+ dependencies:
+ '@mikro-orm/core': 6.6.11
+ '@mikro-orm/knex': 6.6.11(@mikro-orm/core@6.6.11)(mariadb@3.4.5)(mysql2@3.20.0(@types/node@24.12.0))(pg@8.20.0)
+ mysql2: 3.20.0(@types/node@24.12.0)
+ transitivePeerDependencies:
+ - '@types/node'
+ - better-sqlite3
+ - libsql
+ - mariadb
+ - mysql
+ - pg
+ - pg-native
+ - pg-query-stream
+ - sqlite3
+ - supports-color
+ - tedious
+
+ '@mikro-orm/postgresql@6.6.11(@mikro-orm/core@6.6.11)(mariadb@3.4.5)':
+ dependencies:
+ '@mikro-orm/core': 6.6.11
+ '@mikro-orm/knex': 6.6.11(@mikro-orm/core@6.6.11)(mariadb@3.4.5)(pg@8.20.0)
+ pg: 8.20.0
+ postgres-array: 3.0.4
+ postgres-date: 2.1.0
+ postgres-interval: 4.0.2
+ transitivePeerDependencies:
+ - better-sqlite3
+ - libsql
+ - mariadb
+ - mysql
+ - mysql2
+ - pg-native
+ - pg-query-stream
+ - sqlite3
+ - supports-color
+ - tedious
+
+ '@mikro-orm/reflection@6.6.11(@mikro-orm/core@6.6.11)':
+ dependencies:
+ '@mikro-orm/core': 6.6.11
+ globby: 11.1.0
+ ts-morph: 27.0.2
+
+ '@mikro-orm/sqlite@6.6.11(@mikro-orm/core@6.6.11)(mariadb@3.4.5)(pg@8.20.0)':
+ dependencies:
+ '@mikro-orm/core': 6.6.11
+ '@mikro-orm/knex': 6.6.11(@mikro-orm/core@6.6.11)(mariadb@3.4.5)(pg@8.20.0)(sqlite3@5.1.7)
+ fs-extra: 11.3.3
+ sqlite3: 5.1.7
+ sqlstring-sqlite: 0.1.1
+ transitivePeerDependencies:
+ - better-sqlite3
+ - bluebird
+ - libsql
+ - mariadb
+ - mysql
+ - mysql2
+ - pg
+ - pg-native
+ - pg-query-stream
+ - supports-color
+ - tedious
+
+ '@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)':
+ dependencies:
+ '@hono/node-server': 1.19.12(hono@4.12.9)
+ ajv: 8.18.0
+ ajv-formats: 3.0.1(ajv@8.18.0)
+ content-type: 1.0.5
+ cors: 2.8.6
+ cross-spawn: 7.0.6
+ eventsource: 3.0.7
+ eventsource-parser: 3.0.6
+ express: 5.2.1
+ express-rate-limit: 8.3.2(express@5.2.1)
+ hono: 4.12.9
+ jose: 6.2.2
+ json-schema-typed: 8.0.2
+ pkce-challenge: 5.0.1
+ raw-body: 3.0.2
+ zod: 4.3.6
+ zod-to-json-schema: 3.25.2(zod@4.3.6)
+ optionalDependencies:
+ '@cfworker/json-schema': 4.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@napi-rs/wasm-runtime@1.1.2(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)':
+ dependencies:
+ '@emnapi/core': 1.9.1
+ '@emnapi/runtime': 1.9.1
+ '@tybys/wasm-util': 0.10.1
+ optional: true
+
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.20.1
+
+ '@npmcli/fs@1.1.1':
+ dependencies:
+ '@gar/promisify': 1.1.3
+ semver: 7.7.4
+ optional: true
+
+ '@npmcli/move-file@1.1.2':
+ dependencies:
+ mkdirp: 1.0.4
+ rimraf: 3.0.2
+ optional: true
+
+ '@openai/agents-core@0.8.2(@cfworker/json-schema@4.1.1)(ws@8.20.0)(zod@4.3.6)':
+ dependencies:
+ debug: 4.4.3
+ openai: 6.33.0(ws@8.20.0)(zod@4.3.6)
+ optionalDependencies:
+ '@modelcontextprotocol/sdk': 1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)
+ zod: 4.3.6
+ transitivePeerDependencies:
+ - '@cfworker/json-schema'
+ - supports-color
+ - ws
+
+ '@openai/agents-openai@0.8.2(@cfworker/json-schema@4.1.1)(ws@8.20.0)(zod@4.3.6)':
+ dependencies:
+ '@openai/agents-core': 0.8.2(@cfworker/json-schema@4.1.1)(ws@8.20.0)(zod@4.3.6)
+ debug: 4.4.3
+ openai: 6.33.0(ws@8.20.0)(zod@4.3.6)
+ zod: 4.3.6
+ transitivePeerDependencies:
+ - '@cfworker/json-schema'
+ - supports-color
+ - ws
+
+ '@openai/agents-realtime@0.8.2(@cfworker/json-schema@4.1.1)(zod@4.3.6)':
+ dependencies:
+ '@openai/agents-core': 0.8.2(@cfworker/json-schema@4.1.1)(ws@8.20.0)(zod@4.3.6)
+ '@types/ws': 8.18.1
+ debug: 4.4.3
+ ws: 8.20.0
+ zod: 4.3.6
+ transitivePeerDependencies:
+ - '@cfworker/json-schema'
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ '@openai/agents@0.8.2(@cfworker/json-schema@4.1.1)(ws@8.20.0)(zod@4.3.6)':
+ dependencies:
+ '@openai/agents-core': 0.8.2(@cfworker/json-schema@4.1.1)(ws@8.20.0)(zod@4.3.6)
+ '@openai/agents-openai': 0.8.2(@cfworker/json-schema@4.1.1)(ws@8.20.0)(zod@4.3.6)
+ '@openai/agents-realtime': 0.8.2(@cfworker/json-schema@4.1.1)(zod@4.3.6)
+ debug: 4.4.3
+ openai: 6.33.0(ws@8.20.0)(zod@4.3.6)
+ zod: 4.3.6
+ transitivePeerDependencies:
+ - '@cfworker/json-schema'
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ - ws
+
+ '@opencode-ai/plugin@1.3.13':
+ dependencies:
+ '@opencode-ai/sdk': 1.3.13
+ zod: 4.1.8
+
+ '@opencode-ai/sdk@1.3.13': {}
+
+ '@opentelemetry/api-logs@0.205.0':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+
+ '@opentelemetry/api-logs@0.213.0':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+
+ '@opentelemetry/api@1.9.1': {}
+
+ '@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+
+ '@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/semantic-conventions': 1.40.0
+
+ '@opentelemetry/core@2.6.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/semantic-conventions': 1.40.0
+
+ '@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/semantic-conventions': 1.40.0
+
+ '@opentelemetry/exporter-logs-otlp-http@0.205.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/api-logs': 0.205.0
+ '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-exporter-base': 0.205.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-transformer': 0.205.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-logs': 0.205.0(@opentelemetry/api@1.9.1)
+
+ '@opentelemetry/exporter-metrics-otlp-grpc@0.213.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@grpc/grpc-js': 1.14.3
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/exporter-metrics-otlp-http': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-exporter-base': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-grpc-exporter-base': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-metrics': 2.6.0(@opentelemetry/api@1.9.1)
+
+ '@opentelemetry/exporter-metrics-otlp-http@0.213.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-exporter-base': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-metrics': 2.6.0(@opentelemetry/api@1.9.1)
+
+ '@opentelemetry/exporter-trace-otlp-grpc@0.213.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@grpc/grpc-js': 1.14.3
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-exporter-base': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-grpc-exporter-base': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.1)
+
+ '@opentelemetry/exporter-trace-otlp-http@0.213.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-exporter-base': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.1)
+
+ '@opentelemetry/otlp-exporter-base@0.205.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-transformer': 0.205.0(@opentelemetry/api@1.9.1)
+
+ '@opentelemetry/otlp-exporter-base@0.213.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.1)
+
+ '@opentelemetry/otlp-grpc-exporter-base@0.213.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@grpc/grpc-js': 1.14.3
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-exporter-base': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.1)
+
+ '@opentelemetry/otlp-transformer@0.205.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/api-logs': 0.205.0
+ '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-logs': 0.205.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-metrics': 2.1.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-trace-base': 2.1.0(@opentelemetry/api@1.9.1)
+ protobufjs: 7.5.4
+
+ '@opentelemetry/otlp-transformer@0.213.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/api-logs': 0.213.0
+ '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-logs': 0.213.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-metrics': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.1)
+ protobufjs: 7.5.4
+
+ '@opentelemetry/resource-detector-gcp@0.40.3(@opentelemetry/api@1.9.1)(encoding@0.1.13)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1)
+ gcp-metadata: 6.1.1(encoding@0.1.13)
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ '@opentelemetry/resources@2.1.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/semantic-conventions': 1.40.0
+
+ '@opentelemetry/resources@2.6.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/semantic-conventions': 1.40.0
+
+ '@opentelemetry/resources@2.6.1(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/semantic-conventions': 1.40.0
+
+ '@opentelemetry/sdk-logs@0.205.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/api-logs': 0.205.0
+ '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.1)
+
+ '@opentelemetry/sdk-logs@0.213.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/api-logs': 0.213.0
+ '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/semantic-conventions': 1.40.0
+
+ '@opentelemetry/sdk-metrics@2.1.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.1)
+
+ '@opentelemetry/sdk-metrics@2.6.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.1)
+
+ '@opentelemetry/sdk-metrics@2.6.1(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1)
+
+ '@opentelemetry/sdk-trace-base@2.1.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.1.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/semantic-conventions': 1.40.0
+
+ '@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.1)
+ '@opentelemetry/semantic-conventions': 1.40.0
+
+ '@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/resources': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/semantic-conventions': 1.40.0
+
+ '@opentelemetry/sdk-trace-node@2.6.1(@opentelemetry/api@1.9.1)':
+ dependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/context-async-hooks': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/core': 2.6.1(@opentelemetry/api@1.9.1)
+ '@opentelemetry/sdk-trace-base': 2.6.1(@opentelemetry/api@1.9.1)
+
+ '@opentelemetry/semantic-conventions@1.40.0': {}
+
+ '@oxc-project/types@0.122.0': {}
+
+ '@protobufjs/aspromise@1.1.2': {}
+
+ '@protobufjs/base64@1.1.2': {}
+
+ '@protobufjs/codegen@2.0.4': {}
+
+ '@protobufjs/eventemitter@1.1.0': {}
+
+ '@protobufjs/fetch@1.1.0':
+ dependencies:
+ '@protobufjs/aspromise': 1.1.2
+ '@protobufjs/inquire': 1.1.0
+
+ '@protobufjs/float@1.0.2': {}
+
+ '@protobufjs/inquire@1.1.0': {}
+
+ '@protobufjs/path@1.1.2': {}
+
+ '@protobufjs/pool@1.1.0': {}
+
+ '@protobufjs/utf8@1.1.0': {}
+
+ '@rolldown/binding-android-arm64@1.0.0-rc.12':
+ optional: true
+
+ '@rolldown/binding-darwin-arm64@1.0.0-rc.12':
+ optional: true
+
+ '@rolldown/binding-darwin-x64@1.0.0-rc.12':
+ optional: true
+
+ '@rolldown/binding-freebsd-x64@1.0.0-rc.12':
+ optional: true
+
+ '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12':
+ optional: true
+
+ '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12':
+ optional: true
+
+ '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12':
+ optional: true
+
+ '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12':
+ optional: true
+
+ '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12':
+ optional: true
+
+ '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12':
+ optional: true
+
+ '@rolldown/binding-linux-x64-musl@1.0.0-rc.12':
+ optional: true
+
+ '@rolldown/binding-openharmony-arm64@1.0.0-rc.12':
+ optional: true
+
+ '@rolldown/binding-wasm32-wasi@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)':
+ dependencies:
+ '@napi-rs/wasm-runtime': 1.1.2(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ optional: true
+
+ '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12':
+ optional: true
+
+ '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12':
+ optional: true
+
+ '@rolldown/pluginutils@1.0.0-rc.12': {}
+
+ '@selderee/plugin-htmlparser2@0.11.0':
+ dependencies:
+ domhandler: 5.0.3
+ selderee: 0.11.0
+
+ '@smithy/is-array-buffer@2.2.0':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/types@4.13.1':
+ dependencies:
+ tslib: 2.8.1
+
+ '@smithy/util-buffer-from@2.2.0':
+ dependencies:
+ '@smithy/is-array-buffer': 2.2.0
+ tslib: 2.8.1
+
+ '@smithy/util-utf8@2.3.0':
+ dependencies:
+ '@smithy/util-buffer-from': 2.2.0
+ tslib: 2.8.1
+
+ '@so-ric/colorspace@1.1.6':
+ dependencies:
+ color: 5.0.3
+ text-hex: 1.0.0
+
+ '@standard-schema/spec@1.1.0': {}
+
+ '@tootallnate/once@1.1.2':
+ optional: true
+
+ '@tootallnate/once@2.0.0': {}
+
+ '@ts-morph/common@0.28.1':
+ dependencies:
+ minimatch: 10.2.5
+ path-browserify: 1.0.1
+ tinyglobby: 0.2.15
+
+ '@tybys/wasm-util@0.10.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@types/caseless@0.12.5': {}
+
+ '@types/chai@5.2.3':
+ dependencies:
+ '@types/deep-eql': 4.0.2
+ assertion-error: 2.0.1
+
+ '@types/deep-eql@4.0.2': {}
+
+ '@types/estree@1.0.8': {}
+
+ '@types/geojson@7946.0.16': {}
+
+ '@types/json-schema@7.0.15': {}
+
+ '@types/lodash@4.17.24': {}
+
+ '@types/node@24.12.0':
+ dependencies:
+ undici-types: 7.16.0
+
+ '@types/readable-stream@4.0.23':
+ dependencies:
+ '@types/node': 24.12.0
+
+ '@types/request@2.48.13':
+ dependencies:
+ '@types/caseless': 0.12.5
+ '@types/node': 24.12.0
+ '@types/tough-cookie': 4.0.5
+ form-data: 2.5.5
+
+ '@types/retry@0.12.0': {}
+
+ '@types/tough-cookie@4.0.5': {}
+
+ '@types/triple-beam@1.3.5': {}
+
+ '@types/uuid@10.0.0': {}
+
+ '@types/ws@8.18.1':
+ dependencies:
+ '@types/node': 24.12.0
+
+ '@typespec/ts-http-runtime@0.3.4':
+ dependencies:
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
+ tslib: 2.8.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@vitest/expect@4.1.2':
+ dependencies:
+ '@standard-schema/spec': 1.1.0
+ '@types/chai': 5.2.3
+ '@vitest/spy': 4.1.2
+ '@vitest/utils': 4.1.2
+ chai: 6.2.2
+ tinyrainbow: 3.1.0
+
+ '@vitest/mocker@4.1.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.4))':
+ dependencies:
+ '@vitest/spy': 4.1.2
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ optionalDependencies:
+ vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.4)
+
+ '@vitest/pretty-format@4.1.2':
+ dependencies:
+ tinyrainbow: 3.1.0
+
+ '@vitest/runner@4.1.2':
+ dependencies:
+ '@vitest/utils': 4.1.2
+ pathe: 2.0.3
+
+ '@vitest/snapshot@4.1.2':
+ dependencies:
+ '@vitest/pretty-format': 4.1.2
+ '@vitest/utils': 4.1.2
+ magic-string: 0.30.21
+ pathe: 2.0.3
+
+ '@vitest/spy@4.1.2': {}
+
+ '@vitest/utils@4.1.2':
+ dependencies:
+ '@vitest/pretty-format': 4.1.2
+ convert-source-map: 2.0.0
+ tinyrainbow: 3.1.0
+
+ abbrev@1.1.1:
+ optional: true
+
+ abort-controller@3.0.0:
+ dependencies:
+ event-target-shim: 5.0.1
+
+ accepts@2.0.0:
+ dependencies:
+ mime-types: 3.0.2
+ negotiator: 1.0.0
+
+ agent-base@6.0.2:
+ dependencies:
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ agent-base@7.1.4: {}
+
+ agentkeepalive@4.6.0:
+ dependencies:
+ humanize-ms: 1.2.1
+ optional: true
+
+ aggregate-error@3.1.0:
+ dependencies:
+ clean-stack: 2.2.0
+ indent-string: 4.0.0
+ optional: true
+
+ ajv-formats@3.0.1(ajv@8.18.0):
+ optionalDependencies:
+ ajv: 8.18.0
+
+ ajv@8.18.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-uri: 3.1.0
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+
+ ansi-regex@5.0.1: {}
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ ansi-styles@5.2.0: {}
+
+ aproba@2.1.0:
+ optional: true
+
+ are-we-there-yet@3.0.1:
+ dependencies:
+ delegates: 1.0.0
+ readable-stream: 3.6.2
+ optional: true
+
+ array-union@2.1.0: {}
+
+ arrify@2.0.1: {}
+
+ assertion-error@2.0.1: {}
+
+ async-retry@1.3.3:
+ dependencies:
+ retry: 0.13.1
+
+ async@3.2.6: {}
+
+ asynckit@0.4.0: {}
+
+ aws-ssl-profiles@1.1.2: {}
+
+ balanced-match@1.0.2:
+ optional: true
+
+ balanced-match@4.0.4: {}
+
+ base64-js@1.5.1: {}
+
+ bignumber.js@9.3.1: {}
+
+ bindings@1.5.0:
+ dependencies:
+ file-uri-to-path: 1.0.0
+
+ bl@4.1.0:
+ dependencies:
+ buffer: 5.7.1
+ inherits: 2.0.4
+ readable-stream: 3.6.2
+
+ bl@6.1.6:
+ dependencies:
+ '@types/readable-stream': 4.0.23
+ buffer: 6.0.3
+ inherits: 2.0.4
+ readable-stream: 4.7.0
+
+ body-parser@2.2.2:
+ dependencies:
+ bytes: 3.1.2
+ content-type: 1.0.5
+ debug: 4.4.3
+ http-errors: 2.0.1
+ iconv-lite: 0.7.2
+ on-finished: 2.4.1
+ qs: 6.15.0
+ raw-body: 3.0.2
+ type-is: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+
+ brace-expansion@1.1.13:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+ optional: true
+
+ brace-expansion@5.0.5:
+ dependencies:
+ balanced-match: 4.0.4
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
+ buffer-equal-constant-time@1.0.1: {}
+
+ buffer@5.7.1:
+ dependencies:
+ base64-js: 1.5.1
+ ieee754: 1.2.1
+
+ buffer@6.0.3:
+ dependencies:
+ base64-js: 1.5.1
+ ieee754: 1.2.1
+
+ bundle-name@4.1.0:
+ dependencies:
+ run-applescript: 7.1.0
+
+ bytes@3.1.2: {}
+
+ cacache@15.3.0:
+ dependencies:
+ '@npmcli/fs': 1.1.1
+ '@npmcli/move-file': 1.1.2
+ chownr: 2.0.0
+ fs-minipass: 2.1.0
+ glob: 7.2.3
+ infer-owner: 1.0.4
+ lru-cache: 6.0.0
+ minipass: 3.3.6
+ minipass-collect: 1.0.2
+ minipass-flush: 1.0.7
+ minipass-pipeline: 1.2.4
+ mkdirp: 1.0.4
+ p-map: 4.0.0
+ promise-inflight: 1.0.1
+ rimraf: 3.0.2
+ ssri: 8.0.1
+ tar: 6.2.1
+ unique-filename: 1.1.1
+ transitivePeerDependencies:
+ - bluebird
+ optional: true
+
+ call-bind-apply-helpers@1.0.2:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
+ call-bound@1.0.4:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ get-intrinsic: 1.3.0
+
+ camelcase@6.3.0: {}
+
+ chai@6.2.2: {}
+
+ chalk@5.6.2: {}
+
+ chownr@1.1.4: {}
+
+ chownr@2.0.0: {}
+
+ clean-stack@2.2.0:
+ optional: true
+
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
+ code-block-writer@13.0.3: {}
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-convert@3.1.3:
+ dependencies:
+ color-name: 2.1.0
+
+ color-name@1.1.4: {}
+
+ color-name@2.1.0: {}
+
+ color-string@2.1.4:
+ dependencies:
+ color-name: 2.1.0
+
+ color-support@1.1.3:
+ optional: true
+
+ color@5.0.3:
+ dependencies:
+ color-convert: 3.1.3
+ color-string: 2.1.4
+
+ colorette@2.0.19: {}
+
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
+ commander@10.0.1: {}
+
+ concat-map@0.0.1:
+ optional: true
+
+ console-control-strings@1.1.0:
+ optional: true
+
+ console-table-printer@2.15.0:
+ dependencies:
+ simple-wcswidth: 1.1.2
+
+ content-disposition@1.0.1: {}
+
+ content-type@1.0.5: {}
+
+ convert-source-map@2.0.0: {}
+
+ cookie-signature@1.2.2: {}
+
+ cookie@0.7.2: {}
+
+ cors@2.8.6:
+ dependencies:
+ object-assign: 4.1.1
+ vary: 1.1.2
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ data-uri-to-buffer@4.0.1: {}
+
+ dataloader@2.2.3: {}
+
+ debug@4.3.4:
+ dependencies:
+ ms: 2.1.2
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ decamelize@1.2.0: {}
+
+ decompress-response@6.0.0:
+ dependencies:
+ mimic-response: 3.1.0
+
+ deep-extend@0.6.0: {}
+
+ deepmerge@4.3.1: {}
+
+ default-browser-id@5.0.1: {}
+
+ default-browser@5.5.0:
+ dependencies:
+ bundle-name: 4.1.0
+ default-browser-id: 5.0.1
+
+ define-lazy-prop@3.0.0: {}
+
+ delayed-stream@1.0.0: {}
+
+ delegates@1.0.0:
+ optional: true
+
+ denque@2.1.0: {}
+
+ depd@2.0.0: {}
+
+ detect-libc@2.1.2: {}
+
+ dir-glob@3.0.1:
+ dependencies:
+ path-type: 4.0.0
+
+ dom-serializer@2.0.0:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ entities: 4.5.0
+
+ domelementtype@2.3.0: {}
+
+ domhandler@5.0.3:
+ dependencies:
+ domelementtype: 2.3.0
+
+ domutils@3.2.2:
+ dependencies:
+ dom-serializer: 2.0.0
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+
+ dotenv@17.3.1: {}
+
+ dunder-proto@1.0.1:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ duplexify@4.1.3:
+ dependencies:
+ end-of-stream: 1.4.5
+ inherits: 2.0.4
+ readable-stream: 3.6.2
+ stream-shift: 1.0.3
+
+ ecdsa-sig-formatter@1.0.11:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ ee-first@1.1.1: {}
+
+ emoji-regex@8.0.0: {}
+
+ enabled@2.0.0: {}
+
+ encodeurl@2.0.0: {}
+
+ encoding@0.1.13:
+ dependencies:
+ iconv-lite: 0.6.3
+ optional: true
+
+ end-of-stream@1.4.5:
+ dependencies:
+ once: 1.4.0
+
+ entities@4.5.0: {}
+
+ env-paths@2.2.1:
+ optional: true
+
+ err-code@2.0.3:
+ optional: true
+
+ es-define-property@1.0.1: {}
+
+ es-errors@1.3.0: {}
+
+ es-module-lexer@2.0.0: {}
+
+ es-object-atoms@1.1.1:
+ dependencies:
+ es-errors: 1.3.0
+
+ es-set-tostringtag@2.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
+ esbuild@0.27.4:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.27.4
+ '@esbuild/android-arm': 0.27.4
+ '@esbuild/android-arm64': 0.27.4
+ '@esbuild/android-x64': 0.27.4
+ '@esbuild/darwin-arm64': 0.27.4
+ '@esbuild/darwin-x64': 0.27.4
+ '@esbuild/freebsd-arm64': 0.27.4
+ '@esbuild/freebsd-x64': 0.27.4
+ '@esbuild/linux-arm': 0.27.4
+ '@esbuild/linux-arm64': 0.27.4
+ '@esbuild/linux-ia32': 0.27.4
+ '@esbuild/linux-loong64': 0.27.4
+ '@esbuild/linux-mips64el': 0.27.4
+ '@esbuild/linux-ppc64': 0.27.4
+ '@esbuild/linux-riscv64': 0.27.4
+ '@esbuild/linux-s390x': 0.27.4
+ '@esbuild/linux-x64': 0.27.4
+ '@esbuild/netbsd-arm64': 0.27.4
+ '@esbuild/netbsd-x64': 0.27.4
+ '@esbuild/openbsd-arm64': 0.27.4
+ '@esbuild/openbsd-x64': 0.27.4
+ '@esbuild/openharmony-arm64': 0.27.4
+ '@esbuild/sunos-x64': 0.27.4
+ '@esbuild/win32-arm64': 0.27.4
+ '@esbuild/win32-ia32': 0.27.4
+ '@esbuild/win32-x64': 0.27.4
+
+ escalade@3.2.0: {}
+
+ escape-html@1.0.3: {}
+
+ esm@3.2.25: {}
+
+ esprima@4.0.1: {}
+
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.8
+
+ etag@1.8.1: {}
+
+ event-target-shim@5.0.1: {}
+
+ eventemitter3@4.0.7: {}
+
+ eventemitter3@5.0.4: {}
+
+ events@3.3.0: {}
+
+ eventsource-parser@3.0.6: {}
+
+ eventsource@3.0.7:
+ dependencies:
+ eventsource-parser: 3.0.6
+
+ expand-template@2.0.3: {}
+
+ expect-type@1.3.0: {}
+
+ express-rate-limit@8.3.2(express@5.2.1):
+ dependencies:
+ express: 5.2.1
+ ip-address: 10.1.0
+
+ express@5.2.1:
+ dependencies:
+ accepts: 2.0.0
+ body-parser: 2.2.2
+ content-disposition: 1.0.1
+ content-type: 1.0.5
+ cookie: 0.7.2
+ cookie-signature: 1.2.2
+ debug: 4.4.3
+ depd: 2.0.0
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ finalhandler: 2.1.1
+ fresh: 2.0.0
+ http-errors: 2.0.1
+ merge-descriptors: 2.0.0
+ mime-types: 3.0.2
+ on-finished: 2.4.1
+ once: 1.4.0
+ parseurl: 1.3.3
+ proxy-addr: 2.0.7
+ qs: 6.15.0
+ range-parser: 1.2.1
+ router: 2.2.0
+ send: 1.2.1
+ serve-static: 2.2.1
+ statuses: 2.0.2
+ type-is: 2.0.1
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ extend@3.0.2: {}
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-glob@3.3.3:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fast-uri@3.1.0: {}
+
+ fast-xml-builder@1.1.4:
+ dependencies:
+ path-expression-matcher: 1.2.0
+
+ fast-xml-parser@5.5.9:
+ dependencies:
+ fast-xml-builder: 1.1.4
+ path-expression-matcher: 1.2.0
+ strnum: 2.2.2
+
+ fastq@1.20.1:
+ dependencies:
+ reusify: 1.1.0
+
+ fdir@6.5.0(picomatch@4.0.4):
+ optionalDependencies:
+ picomatch: 4.0.4
+
+ fecha@4.2.3: {}
+
+ fetch-blob@3.2.0:
+ dependencies:
+ node-domexception: 1.0.0
+ web-streams-polyfill: 3.3.3
+
+ file-uri-to-path@1.0.0: {}
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ finalhandler@2.1.1:
+ dependencies:
+ debug: 4.4.3
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ statuses: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ fn.name@1.1.0: {}
+
+ form-data@2.5.5:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ es-set-tostringtag: 2.1.0
+ hasown: 2.0.2
+ mime-types: 2.1.35
+ safe-buffer: 5.2.1
+
+ formdata-polyfill@4.0.10:
+ dependencies:
+ fetch-blob: 3.2.0
+
+ forwarded@0.2.0: {}
+
+ fresh@2.0.0: {}
+
+ fs-constants@1.0.0: {}
+
+ fs-extra@11.3.3:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.2.0
+ universalify: 2.0.1
+
+ fs-minipass@2.1.0:
+ dependencies:
+ minipass: 3.3.6
+
+ fs.realpath@1.0.0:
+ optional: true
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ gauge@4.0.4:
+ dependencies:
+ aproba: 2.1.0
+ color-support: 1.1.3
+ console-control-strings: 1.1.0
+ has-unicode: 2.0.1
+ signal-exit: 3.0.7
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wide-align: 1.1.5
+ optional: true
+
+ gaxios@6.7.1(encoding@0.1.13):
+ dependencies:
+ extend: 3.0.2
+ https-proxy-agent: 7.0.6
+ is-stream: 2.0.1
+ node-fetch: 2.7.0(encoding@0.1.13)
+ uuid: 9.0.1
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ gaxios@7.1.4:
+ dependencies:
+ extend: 3.0.2
+ https-proxy-agent: 7.0.6
+ node-fetch: 3.3.2
+ transitivePeerDependencies:
+ - supports-color
+
+ gcp-metadata@6.1.1(encoding@0.1.13):
+ dependencies:
+ gaxios: 6.7.1(encoding@0.1.13)
+ google-logging-utils: 0.0.2
+ json-bigint: 1.0.0
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ gcp-metadata@8.1.2:
+ dependencies:
+ gaxios: 7.1.4
+ google-logging-utils: 1.1.3
+ json-bigint: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ generate-function@2.3.1:
+ dependencies:
+ is-property: 1.0.2
+
+ get-caller-file@2.0.5: {}
+
+ get-intrinsic@1.3.0:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ function-bind: 1.1.2
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ math-intrinsics: 1.1.0
+
+ get-package-type@0.1.0: {}
+
+ get-proto@1.0.1:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-object-atoms: 1.1.1
+
+ getopts@2.3.0: {}
+
+ github-from-package@0.0.0: {}
+
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob@7.2.3:
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.5
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+ optional: true
+
+ globby@11.1.0:
+ dependencies:
+ array-union: 2.1.0
+ dir-glob: 3.0.1
+ fast-glob: 3.3.3
+ ignore: 5.3.2
+ merge2: 1.4.1
+ slash: 3.0.0
+
+ google-auth-library@10.6.2:
+ dependencies:
+ base64-js: 1.5.1
+ ecdsa-sig-formatter: 1.0.11
+ gaxios: 7.1.4
+ gcp-metadata: 8.1.2
+ google-logging-utils: 1.1.3
+ jws: 4.0.1
+ transitivePeerDependencies:
+ - supports-color
+
+ google-auth-library@9.15.1(encoding@0.1.13):
+ dependencies:
+ base64-js: 1.5.1
+ ecdsa-sig-formatter: 1.0.11
+ gaxios: 6.7.1(encoding@0.1.13)
+ gcp-metadata: 6.1.1(encoding@0.1.13)
+ gtoken: 7.1.0(encoding@0.1.13)
+ jws: 4.0.1
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ google-logging-utils@0.0.2: {}
+
+ google-logging-utils@1.1.3: {}
+
+ googleapis-common@7.2.0(encoding@0.1.13):
+ dependencies:
+ extend: 3.0.2
+ gaxios: 6.7.1(encoding@0.1.13)
+ google-auth-library: 9.15.1(encoding@0.1.13)
+ qs: 6.15.0
+ url-template: 2.0.8
+ uuid: 9.0.1
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ googleapis@137.1.0(encoding@0.1.13):
+ dependencies:
+ google-auth-library: 9.15.1(encoding@0.1.13)
+ googleapis-common: 7.2.0(encoding@0.1.13)
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ gopd@1.2.0: {}
+
+ graceful-fs@4.2.11: {}
+
+ gtoken@7.1.0(encoding@0.1.13):
+ dependencies:
+ gaxios: 6.7.1(encoding@0.1.13)
+ jws: 4.0.1
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ has-symbols@1.1.0: {}
+
+ has-tostringtag@1.0.2:
+ dependencies:
+ has-symbols: 1.1.0
+
+ has-unicode@2.0.1:
+ optional: true
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
+ hono@4.12.9: {}
+
+ html-entities@2.6.0: {}
+
+ html-to-text@9.0.5:
+ dependencies:
+ '@selderee/plugin-htmlparser2': 0.11.0
+ deepmerge: 4.3.1
+ dom-serializer: 2.0.0
+ htmlparser2: 8.0.2
+ selderee: 0.11.0
+
+ htmlparser2@8.0.2:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ domutils: 3.2.2
+ entities: 4.5.0
+
+ http-cache-semantics@4.2.0:
+ optional: true
+
+ http-errors@2.0.1:
+ dependencies:
+ depd: 2.0.0
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 2.0.2
+ toidentifier: 1.0.1
+
+ http-proxy-agent@4.0.1:
+ dependencies:
+ '@tootallnate/once': 1.1.2
+ agent-base: 6.0.2
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
+ http-proxy-agent@5.0.0:
+ dependencies:
+ '@tootallnate/once': 2.0.0
+ agent-base: 6.0.2
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ http-proxy-agent@7.0.2:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@5.0.1:
+ dependencies:
+ agent-base: 6.0.2
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@7.0.6:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ humanize-ms@1.2.1:
+ dependencies:
+ ms: 2.1.3
+ optional: true
+
+ iconv-lite@0.6.3:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ iconv-lite@0.7.2:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ ieee754@1.2.1: {}
+
+ ignore@5.3.2: {}
+
+ imurmurhash@0.1.4:
+ optional: true
+
+ indent-string@4.0.0:
+ optional: true
+
+ infer-owner@1.0.4:
+ optional: true
+
+ inflight@1.0.6:
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+ optional: true
+
+ inherits@2.0.4: {}
+
+ ini@1.3.8: {}
+
+ interpret@2.2.0: {}
+
+ ip-address@10.1.0: {}
+
+ ipaddr.js@1.9.1: {}
+
+ is-core-module@2.16.1:
+ dependencies:
+ hasown: 2.0.2
+
+ is-docker@3.0.0: {}
+
+ is-extglob@2.1.1: {}
+
+ is-fullwidth-code-point@3.0.0: {}
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-inside-container@1.0.0:
+ dependencies:
+ is-docker: 3.0.0
+
+ is-lambda@1.0.1:
+ optional: true
+
+ is-network-error@1.3.1: {}
+
+ is-number@7.0.0: {}
+
+ is-promise@4.0.0: {}
+
+ is-property@1.0.2: {}
+
+ is-stream@2.0.1: {}
+
+ is-wsl@3.1.1:
+ dependencies:
+ is-inside-container: 1.0.0
+
+ isexe@2.0.0: {}
+
+ jose@6.2.2: {}
+
+ js-md4@0.3.2: {}
+
+ js-tiktoken@1.0.21:
+ dependencies:
+ base64-js: 1.5.1
+
+ json-bigint@1.0.0:
+ dependencies:
+ bignumber.js: 9.3.1
+
+ json-schema-to-ts@3.1.1:
+ dependencies:
+ '@babel/runtime': 7.29.2
+ ts-algebra: 2.0.0
+
+ json-schema-traverse@1.0.0: {}
+
+ json-schema-typed@8.0.2: {}
+
+ jsonfile@6.2.0:
+ dependencies:
+ universalify: 2.0.1
+ optionalDependencies:
+ graceful-fs: 4.2.11
+
+ jsonwebtoken@9.0.3:
+ dependencies:
+ jws: 4.0.1
+ lodash.includes: 4.3.0
+ lodash.isboolean: 3.0.3
+ lodash.isinteger: 4.0.4
+ lodash.isnumber: 3.0.3
+ lodash.isplainobject: 4.0.6
+ lodash.isstring: 4.0.1
+ lodash.once: 4.1.1
+ ms: 2.1.3
+ semver: 7.7.4
+
+ jwa@2.0.1:
+ dependencies:
+ buffer-equal-constant-time: 1.0.1
+ ecdsa-sig-formatter: 1.0.11
+ safe-buffer: 5.2.1
+
+ jws@4.0.1:
+ dependencies:
+ jwa: 2.0.1
+ safe-buffer: 5.2.1
+
+ knex@3.2.8(mysql2@3.20.0(@types/node@24.12.0))(pg@8.20.0):
+ dependencies:
+ colorette: 2.0.19
+ commander: 10.0.1
+ debug: 4.3.4
+ escalade: 3.2.0
+ esm: 3.2.25
+ get-package-type: 0.1.0
+ getopts: 2.3.0
+ interpret: 2.2.0
+ lodash: 4.17.21
+ pg-connection-string: 2.6.2
+ rechoir: 0.8.0
+ resolve-from: 5.0.0
+ tarn: 3.0.2
+ tildify: 2.0.0
+ optionalDependencies:
+ mysql2: 3.20.0(@types/node@24.12.0)
+ pg: 8.20.0
+ transitivePeerDependencies:
+ - supports-color
+
+ knex@3.2.8(pg@8.20.0):
+ dependencies:
+ colorette: 2.0.19
+ commander: 10.0.1
+ debug: 4.3.4
+ escalade: 3.2.0
+ esm: 3.2.25
+ get-package-type: 0.1.0
+ getopts: 2.3.0
+ interpret: 2.2.0
+ lodash: 4.17.21
+ pg-connection-string: 2.6.2
+ rechoir: 0.8.0
+ resolve-from: 5.0.0
+ tarn: 3.0.2
+ tildify: 2.0.0
+ optionalDependencies:
+ pg: 8.20.0
+ transitivePeerDependencies:
+ - supports-color
+
+ knex@3.2.8(pg@8.20.0)(sqlite3@5.1.7):
+ dependencies:
+ colorette: 2.0.19
+ commander: 10.0.1
+ debug: 4.3.4
+ escalade: 3.2.0
+ esm: 3.2.25
+ get-package-type: 0.1.0
+ getopts: 2.3.0
+ interpret: 2.2.0
+ lodash: 4.17.21
+ pg-connection-string: 2.6.2
+ rechoir: 0.8.0
+ resolve-from: 5.0.0
+ tarn: 3.0.2
+ tildify: 2.0.0
+ optionalDependencies:
+ pg: 8.20.0
+ sqlite3: 5.1.7
+ transitivePeerDependencies:
+ - supports-color
+
+ knex@3.2.8(pg@8.20.0)(tedious@19.2.1(@azure/core-client@1.10.1)):
+ dependencies:
+ colorette: 2.0.19
+ commander: 10.0.1
+ debug: 4.3.4
+ escalade: 3.2.0
+ esm: 3.2.25
+ get-package-type: 0.1.0
+ getopts: 2.3.0
+ interpret: 2.2.0
+ lodash: 4.17.21
+ pg-connection-string: 2.6.2
+ rechoir: 0.8.0
+ resolve-from: 5.0.0
+ tarn: 3.0.2
+ tildify: 2.0.0
+ optionalDependencies:
+ pg: 8.20.0
+ tedious: 19.2.1(@azure/core-client@1.10.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ kuler@2.0.0: {}
+
+ langsmith@0.5.15(@opentelemetry/api@1.9.1)(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.1))(openai@6.33.0(ws@8.20.0)(zod@4.3.6))(ws@8.20.0):
+ dependencies:
+ '@types/uuid': 10.0.0
+ chalk: 5.6.2
+ console-table-printer: 2.15.0
+ p-queue: 6.6.2
+ semver: 7.7.4
+ uuid: 10.0.0
+ optionalDependencies:
+ '@opentelemetry/api': 1.9.1
+ '@opentelemetry/sdk-trace-base': 2.6.1(@opentelemetry/api@1.9.1)
+ openai: 6.33.0(ws@8.20.0)(zod@4.3.6)
+ ws: 8.20.0
+
+ leac@0.6.0: {}
+
+ lightningcss-android-arm64@1.32.0:
+ optional: true
+
+ lightningcss-darwin-arm64@1.32.0:
+ optional: true
+
+ lightningcss-darwin-x64@1.32.0:
+ optional: true
+
+ lightningcss-freebsd-x64@1.32.0:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.32.0:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.32.0:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.32.0:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.32.0:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.32.0:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.32.0:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.32.0:
+ optional: true
+
+ lightningcss@1.32.0:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.32.0
+ lightningcss-darwin-arm64: 1.32.0
+ lightningcss-darwin-x64: 1.32.0
+ lightningcss-freebsd-x64: 1.32.0
+ lightningcss-linux-arm-gnueabihf: 1.32.0
+ lightningcss-linux-arm64-gnu: 1.32.0
+ lightningcss-linux-arm64-musl: 1.32.0
+ lightningcss-linux-x64-gnu: 1.32.0
+ lightningcss-linux-x64-musl: 1.32.0
+ lightningcss-win32-arm64-msvc: 1.32.0
+ lightningcss-win32-x64-msvc: 1.32.0
+
+ llamaindex@0.12.1(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(hono@4.12.9)(tree-sitter@0.22.4)(web-tree-sitter@0.24.7)(zod@4.3.6):
+ dependencies:
+ '@llamaindex/core': 0.6.22
+ '@llamaindex/env': 0.1.30
+ '@llamaindex/node-parser': 2.0.22(@llamaindex/core@0.6.22)(@llamaindex/env@0.1.30)(tree-sitter@0.22.4)(web-tree-sitter@0.24.7)
+ '@llamaindex/workflow': 1.1.24(@llamaindex/core@0.6.22)(@llamaindex/env@0.1.30)(@modelcontextprotocol/sdk@1.29.0(@cfworker/json-schema@4.1.1)(zod@4.3.6))(hono@4.12.9)(zod@4.3.6)
+ '@types/lodash': 4.17.24
+ '@types/node': 24.12.0
+ lodash: 4.17.21
+ magic-bytes.js: 1.13.0
+ transitivePeerDependencies:
+ - '@huggingface/transformers'
+ - '@modelcontextprotocol/sdk'
+ - gpt-tokenizer
+ - hono
+ - next
+ - p-retry
+ - rxjs
+ - tree-sitter
+ - web-tree-sitter
+ - zod
+
+ lodash-es@4.17.23: {}
+
+ lodash.camelcase@4.3.0: {}
+
+ lodash.includes@4.3.0: {}
+
+ lodash.isboolean@3.0.3: {}
+
+ lodash.isinteger@4.0.4: {}
+
+ lodash.isnumber@3.0.3: {}
+
+ lodash.isplainobject@4.0.6: {}
+
+ lodash.isstring@4.0.1: {}
+
+ lodash.once@4.1.1: {}
+
+ lodash@4.17.21: {}
+
+ logform@2.7.0:
+ dependencies:
+ '@colors/colors': 1.6.0
+ '@types/triple-beam': 1.3.5
+ fecha: 4.2.3
+ ms: 2.1.3
+ safe-stable-stringify: 2.5.0
+ triple-beam: 1.4.1
+
+ long@5.3.2: {}
+
+ lru-cache@10.4.3: {}
+
+ lru-cache@6.0.0:
+ dependencies:
+ yallist: 4.0.0
+ optional: true
+
+ lru.min@1.1.4: {}
+
+ magic-bytes.js@1.13.0: {}
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ make-fetch-happen@9.1.0:
+ dependencies:
+ agentkeepalive: 4.6.0
+ cacache: 15.3.0
+ http-cache-semantics: 4.2.0
+ http-proxy-agent: 4.0.1
+ https-proxy-agent: 5.0.1
+ is-lambda: 1.0.1
+ lru-cache: 6.0.0
+ minipass: 3.3.6
+ minipass-collect: 1.0.2
+ minipass-fetch: 1.4.1
+ minipass-flush: 1.0.7
+ minipass-pipeline: 1.2.4
+ negotiator: 0.6.4
+ promise-retry: 2.0.1
+ socks-proxy-agent: 6.2.1
+ ssri: 8.0.1
+ transitivePeerDependencies:
+ - bluebird
+ - supports-color
+ optional: true
+
+ mariadb@3.4.5:
+ dependencies:
+ '@types/geojson': 7946.0.16
+ '@types/node': 24.12.0
+ denque: 2.1.0
+ iconv-lite: 0.6.3
+ lru-cache: 10.4.3
+
+ math-intrinsics@1.1.0: {}
+
+ media-typer@1.1.0: {}
+
+ merge-descriptors@2.0.0: {}
+
+ merge2@1.4.1: {}
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.2
+
+ mikro-orm@6.6.11: {}
+
+ mime-db@1.52.0: {}
+
+ mime-db@1.54.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
+ mime-types@3.0.2:
+ dependencies:
+ mime-db: 1.54.0
+
+ mime@3.0.0: {}
+
+ mimic-response@3.1.0: {}
+
+ minimatch@10.2.5:
+ dependencies:
+ brace-expansion: 5.0.5
+
+ minimatch@3.1.5:
+ dependencies:
+ brace-expansion: 1.1.13
+ optional: true
+
+ minimist@1.2.8: {}
+
+ minipass-collect@1.0.2:
+ dependencies:
+ minipass: 3.3.6
+ optional: true
+
+ minipass-fetch@1.4.1:
+ dependencies:
+ minipass: 3.3.6
+ minipass-sized: 1.0.3
+ minizlib: 2.1.2
+ optionalDependencies:
+ encoding: 0.1.13
+ optional: true
+
+ minipass-flush@1.0.7:
+ dependencies:
+ minipass: 3.3.6
+ optional: true
+
+ minipass-pipeline@1.2.4:
+ dependencies:
+ minipass: 3.3.6
+ optional: true
+
+ minipass-sized@1.0.3:
+ dependencies:
+ minipass: 3.3.6
+ optional: true
+
+ minipass@3.3.6:
+ dependencies:
+ yallist: 4.0.0
+
+ minipass@5.0.0: {}
+
+ minizlib@2.1.2:
+ dependencies:
+ minipass: 3.3.6
+ yallist: 4.0.0
+
+ mkdirp-classic@0.5.3: {}
+
+ mkdirp@1.0.4: {}
+
+ ms@2.1.2: {}
+
+ ms@2.1.3: {}
+
+ mustache@4.2.0: {}
+
+ mysql2@3.20.0(@types/node@24.12.0):
+ dependencies:
+ '@types/node': 24.12.0
+ aws-ssl-profiles: 1.1.2
+ denque: 2.1.0
+ generate-function: 2.3.1
+ iconv-lite: 0.7.2
+ long: 5.3.2
+ lru.min: 1.1.4
+ named-placeholders: 1.1.6
+ sql-escaper: 1.3.3
+
+ named-placeholders@1.1.6:
+ dependencies:
+ lru.min: 1.1.4
+
+ nanoid@3.3.11: {}
+
+ napi-build-utils@2.0.0: {}
+
+ native-duplexpair@1.0.0: {}
+
+ negotiator@0.6.4:
+ optional: true
+
+ negotiator@1.0.0: {}
+
+ node-abi@3.89.0:
+ dependencies:
+ semver: 7.7.4
+
+ node-addon-api@7.1.1: {}
+
+ node-addon-api@8.7.0: {}
+
+ node-domexception@1.0.0: {}
+
+ node-fetch@2.7.0(encoding@0.1.13):
+ dependencies:
+ whatwg-url: 5.0.0
+ optionalDependencies:
+ encoding: 0.1.13
+
+ node-fetch@3.3.2:
+ dependencies:
+ data-uri-to-buffer: 4.0.1
+ fetch-blob: 3.2.0
+ formdata-polyfill: 4.0.10
+
+ node-gyp-build@4.8.4: {}
+
+ node-gyp@8.4.1:
+ dependencies:
+ env-paths: 2.2.1
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ make-fetch-happen: 9.1.0
+ nopt: 5.0.0
+ npmlog: 6.0.2
+ rimraf: 3.0.2
+ semver: 7.7.4
+ tar: 6.2.1
+ which: 2.0.2
+ transitivePeerDependencies:
+ - bluebird
+ - supports-color
+ optional: true
+
+ nopt@5.0.0:
+ dependencies:
+ abbrev: 1.1.1
+ optional: true
+
+ npmlog@6.0.2:
+ dependencies:
+ are-we-there-yet: 3.0.1
+ console-control-strings: 1.1.0
+ gauge: 4.0.4
+ set-blocking: 2.0.0
+ optional: true
+
+ object-assign@4.1.1: {}
+
+ object-inspect@1.13.4: {}
+
+ obug@2.1.1: {}
+
+ on-finished@2.4.1:
+ dependencies:
+ ee-first: 1.1.1
+
+ once@1.4.0:
+ dependencies:
+ wrappy: 1.0.2
+
+ one-time@1.0.0:
+ dependencies:
+ fn.name: 1.1.0
+
+ open@10.2.0:
+ dependencies:
+ default-browser: 5.5.0
+ define-lazy-prop: 3.0.0
+ is-inside-container: 1.0.0
+ wsl-utils: 0.1.0
+
+ openai@6.33.0(ws@8.20.0)(zod@4.3.6):
+ optionalDependencies:
+ ws: 8.20.0
+ zod: 4.3.6
+
+ p-finally@1.0.0: {}
+
+ p-limit@3.1.0:
+ dependencies:
+ yocto-queue: 0.1.0
+
+ p-map@4.0.0:
+ dependencies:
+ aggregate-error: 3.1.0
+ optional: true
+
+ p-queue@6.6.2:
+ dependencies:
+ eventemitter3: 4.0.7
+ p-timeout: 3.2.0
+
+ p-queue@9.1.0:
+ dependencies:
+ eventemitter3: 5.0.4
+ p-timeout: 7.0.1
+
+ p-retry@4.6.2:
+ dependencies:
+ '@types/retry': 0.12.0
+ retry: 0.13.1
+
+ p-retry@7.1.1:
+ dependencies:
+ is-network-error: 1.3.1
+
+ p-timeout@3.2.0:
+ dependencies:
+ p-finally: 1.0.0
+
+ p-timeout@7.0.1: {}
+
+ parseley@0.12.1:
+ dependencies:
+ leac: 0.6.0
+ peberminta: 0.9.0
+
+ parseurl@1.3.3: {}
+
+ path-browserify@1.0.1: {}
+
+ path-expression-matcher@1.2.0: {}
+
+ path-is-absolute@1.0.1:
+ optional: true
+
+ path-key@3.1.1: {}
+
+ path-parse@1.0.7: {}
+
+ path-to-regexp@8.4.1: {}
+
+ path-type@4.0.0: {}
+
+ pathe@1.1.2: {}
+
+ pathe@2.0.3: {}
+
+ peberminta@0.9.0: {}
+
+ pg-cloudflare@1.3.0:
+ optional: true
+
+ pg-connection-string@2.12.0: {}
+
+ pg-connection-string@2.6.2: {}
+
+ pg-int8@1.0.1: {}
+
+ pg-pool@3.13.0(pg@8.20.0):
+ dependencies:
+ pg: 8.20.0
+
+ pg-protocol@1.13.0: {}
+
+ pg-types@2.2.0:
+ dependencies:
+ pg-int8: 1.0.1
+ postgres-array: 2.0.0
+ postgres-bytea: 1.0.1
+ postgres-date: 1.0.7
+ postgres-interval: 1.2.0
+
+ pg@8.20.0:
+ dependencies:
+ pg-connection-string: 2.12.0
+ pg-pool: 3.13.0(pg@8.20.0)
+ pg-protocol: 1.13.0
+ pg-types: 2.2.0
+ pgpass: 1.0.5
+ optionalDependencies:
+ pg-cloudflare: 1.3.0
+
+ pgpass@1.0.5:
+ dependencies:
+ split2: 4.2.0
+
+ picocolors@1.1.1: {}
+
+ picomatch@2.3.2: {}
+
+ picomatch@4.0.4: {}
+
+ pkce-challenge@5.0.1: {}
+
+ postcss@8.5.8:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ postgres-array@2.0.0: {}
+
+ postgres-array@3.0.4: {}
+
+ postgres-bytea@1.0.1: {}
+
+ postgres-date@1.0.7: {}
+
+ postgres-date@2.1.0: {}
+
+ postgres-interval@1.2.0:
+ dependencies:
+ xtend: 4.0.2
+
+ postgres-interval@4.0.2: {}
+
+ prebuild-install@7.1.3:
+ dependencies:
+ detect-libc: 2.1.2
+ expand-template: 2.0.3
+ github-from-package: 0.0.0
+ minimist: 1.2.8
+ mkdirp-classic: 0.5.3
+ napi-build-utils: 2.0.0
+ node-abi: 3.89.0
+ pump: 3.0.4
+ rc: 1.2.8
+ simple-get: 4.0.1
+ tar-fs: 2.1.4
+ tunnel-agent: 0.6.0
+
+ process@0.11.10: {}
+
+ promise-inflight@1.0.1:
+ optional: true
+
+ promise-retry@2.0.1:
+ dependencies:
+ err-code: 2.0.3
+ retry: 0.12.0
+ optional: true
+
+ protobufjs@7.5.4:
+ dependencies:
+ '@protobufjs/aspromise': 1.1.2
+ '@protobufjs/base64': 1.1.2
+ '@protobufjs/codegen': 2.0.4
+ '@protobufjs/eventemitter': 1.1.0
+ '@protobufjs/fetch': 1.1.0
+ '@protobufjs/float': 1.0.2
+ '@protobufjs/inquire': 1.1.0
+ '@protobufjs/path': 1.1.2
+ '@protobufjs/pool': 1.1.0
+ '@protobufjs/utf8': 1.1.0
+ '@types/node': 24.12.0
+ long: 5.3.2
+
+ proxy-addr@2.0.7:
+ dependencies:
+ forwarded: 0.2.0
+ ipaddr.js: 1.9.1
+
+ pump@3.0.4:
+ dependencies:
+ end-of-stream: 1.4.5
+ once: 1.4.0
+
+ qs@6.15.0:
+ dependencies:
+ side-channel: 1.1.0
+
+ queue-microtask@1.2.3: {}
+
+ range-parser@1.2.1: {}
+
+ raw-body@3.0.2:
+ dependencies:
+ bytes: 3.1.2
+ http-errors: 2.0.1
+ iconv-lite: 0.7.2
+ unpipe: 1.0.0
+
+ rc@1.2.8:
+ dependencies:
+ deep-extend: 0.6.0
+ ini: 1.3.8
+ minimist: 1.2.8
+ strip-json-comments: 2.0.1
+
+ readable-stream@3.6.2:
+ dependencies:
+ inherits: 2.0.4
+ string_decoder: 1.3.0
+ util-deprecate: 1.0.2
+
+ readable-stream@4.7.0:
+ dependencies:
+ abort-controller: 3.0.0
+ buffer: 6.0.3
+ events: 3.3.0
+ process: 0.11.10
+ string_decoder: 1.3.0
+
+ rechoir@0.8.0:
+ dependencies:
+ resolve: 1.22.11
+
+ reflect-metadata@0.2.2: {}
+
+ require-directory@2.1.1: {}
+
+ require-from-string@2.0.2: {}
+
+ resolve-from@5.0.0: {}
+
+ resolve@1.22.11:
+ dependencies:
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ retry-request@7.0.2(encoding@0.1.13):
+ dependencies:
+ '@types/request': 2.48.13
+ extend: 3.0.2
+ teeny-request: 9.0.0(encoding@0.1.13)
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ retry@0.12.0:
+ optional: true
+
+ retry@0.13.1: {}
+
+ reusify@1.1.0: {}
+
+ rimraf@3.0.2:
+ dependencies:
+ glob: 7.2.3
+ optional: true
+
+ rolldown@1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1):
+ dependencies:
+ '@oxc-project/types': 0.122.0
+ '@rolldown/pluginutils': 1.0.0-rc.12
+ optionalDependencies:
+ '@rolldown/binding-android-arm64': 1.0.0-rc.12
+ '@rolldown/binding-darwin-arm64': 1.0.0-rc.12
+ '@rolldown/binding-darwin-x64': 1.0.0-rc.12
+ '@rolldown/binding-freebsd-x64': 1.0.0-rc.12
+ '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.12
+ '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.12
+ '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.12
+ '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.12
+ '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.12
+ '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.12
+ '@rolldown/binding-linux-x64-musl': 1.0.0-rc.12
+ '@rolldown/binding-openharmony-arm64': 1.0.0-rc.12
+ '@rolldown/binding-wasm32-wasi': 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)
+ '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.12
+ '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.12
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+
+ router@2.2.0:
+ dependencies:
+ debug: 4.4.3
+ depd: 2.0.0
+ is-promise: 4.0.0
+ parseurl: 1.3.3
+ path-to-regexp: 8.4.1
+ transitivePeerDependencies:
+ - supports-color
+
+ run-applescript@7.1.0: {}
+
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
+ safe-buffer@5.2.1: {}
+
+ safe-stable-stringify@2.5.0: {}
+
+ safer-buffer@2.1.2: {}
+
+ selderee@0.11.0:
+ dependencies:
+ parseley: 0.12.1
+
+ semver@7.7.4: {}
+
+ send@1.2.1:
+ dependencies:
+ debug: 4.4.3
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ fresh: 2.0.0
+ http-errors: 2.0.1
+ mime-types: 3.0.2
+ ms: 2.1.3
+ on-finished: 2.4.1
+ range-parser: 1.2.1
+ statuses: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ serve-static@2.2.1:
+ dependencies:
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ parseurl: 1.3.3
+ send: 1.2.1
+ transitivePeerDependencies:
+ - supports-color
+
+ set-blocking@2.0.0:
+ optional: true
+
+ setprototypeof@1.2.0: {}
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ side-channel-list@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-map@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-weakmap@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-map: 1.0.1
+
+ side-channel@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-list: 1.0.0
+ side-channel-map: 1.0.1
+ side-channel-weakmap: 1.0.2
+
+ siginfo@2.0.0: {}
+
+ signal-exit@3.0.7:
+ optional: true
+
+ simple-concat@1.0.1: {}
+
+ simple-get@4.0.1:
+ dependencies:
+ decompress-response: 6.0.0
+ once: 1.4.0
+ simple-concat: 1.0.1
+
+ simple-wcswidth@1.1.2: {}
+
+ slash@3.0.0: {}
+
+ smart-buffer@4.2.0:
+ optional: true
+
+ socks-proxy-agent@6.2.1:
+ dependencies:
+ agent-base: 6.0.2
+ debug: 4.4.3
+ socks: 2.8.7
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
+ socks@2.8.7:
+ dependencies:
+ ip-address: 10.1.0
+ smart-buffer: 4.2.0
+ optional: true
+
+ source-map-js@1.2.1: {}
+
+ split2@4.2.0: {}
+
+ sprintf-js@1.1.3: {}
+
+ sql-escaper@1.3.3: {}
+
+ sqlite3@5.1.7:
+ dependencies:
+ bindings: 1.5.0
+ node-addon-api: 7.1.1
+ prebuild-install: 7.1.3
+ tar: 6.2.1
+ optionalDependencies:
+ node-gyp: 8.4.1
+ transitivePeerDependencies:
+ - bluebird
+ - supports-color
+
+ sqlstring-sqlite@0.1.1: {}
+
+ sqlstring@2.3.3: {}
+
+ ssri@8.0.1:
+ dependencies:
+ minipass: 3.3.6
+ optional: true
+
+ stack-trace@0.0.10: {}
+
+ stackback@0.0.2: {}
+
+ statuses@2.0.2: {}
+
+ std-env@4.0.0: {}
+
+ stream-events@1.0.5:
+ dependencies:
+ stubs: 3.0.0
+
+ stream-shift@1.0.3: {}
+
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string_decoder@1.3.0:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-json-comments@2.0.1: {}
+
+ strnum@2.2.2: {}
+
+ stubs@3.0.0: {}
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ tar-fs@2.1.4:
+ dependencies:
+ chownr: 1.1.4
+ mkdirp-classic: 0.5.3
+ pump: 3.0.4
+ tar-stream: 2.2.0
+
+ tar-stream@2.2.0:
+ dependencies:
+ bl: 4.1.0
+ end-of-stream: 1.4.5
+ fs-constants: 1.0.0
+ inherits: 2.0.4
+ readable-stream: 3.6.2
+
+ tar@6.2.1:
+ dependencies:
+ chownr: 2.0.0
+ fs-minipass: 2.1.0
+ minipass: 5.0.0
+ minizlib: 2.1.2
+ mkdirp: 1.0.4
+ yallist: 4.0.0
+
+ tarn@3.0.2: {}
+
+ tedious@19.2.1(@azure/core-client@1.10.1):
+ dependencies:
+ '@azure/core-auth': 1.10.1
+ '@azure/identity': 4.13.1
+ '@azure/keyvault-keys': 4.10.0(@azure/core-client@1.10.1)
+ '@js-joda/core': 5.7.0
+ '@types/node': 24.12.0
+ bl: 6.1.6
+ iconv-lite: 0.7.2
+ js-md4: 0.3.2
+ native-duplexpair: 1.0.0
+ sprintf-js: 1.1.3
+ transitivePeerDependencies:
+ - '@azure/core-client'
+ - supports-color
+
+ teeny-request@9.0.0(encoding@0.1.13):
+ dependencies:
+ http-proxy-agent: 5.0.0
+ https-proxy-agent: 5.0.1
+ node-fetch: 2.7.0(encoding@0.1.13)
+ stream-events: 1.0.5
+ uuid: 9.0.1
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+
+ text-hex@1.0.0: {}
+
+ tildify@2.0.0: {}
+
+ tinybench@2.9.0: {}
+
+ tinyexec@1.0.4: {}
+
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.4)
+ picomatch: 4.0.4
+
+ tinyrainbow@3.1.0: {}
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ toidentifier@1.0.1: {}
+
+ tr46@0.0.3: {}
+
+ tree-sitter@0.22.4:
+ dependencies:
+ node-addon-api: 8.7.0
+ node-gyp-build: 4.8.4
+
+ triple-beam@1.4.1: {}
+
+ ts-algebra@2.0.0: {}
+
+ ts-morph@27.0.2:
+ dependencies:
+ '@ts-morph/common': 0.28.1
+ code-block-writer: 13.0.3
+
+ tslib@2.8.1: {}
+
+ tsqlstring@1.0.1: {}
+
+ tunnel-agent@0.6.0:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ type-is@2.0.1:
+ dependencies:
+ content-type: 1.0.5
+ media-typer: 1.1.0
+ mime-types: 3.0.2
+
+ typescript@6.0.2: {}
+
+ undici-types@7.16.0: {}
+
+ unique-filename@1.1.1:
+ dependencies:
+ unique-slug: 2.0.2
+ optional: true
+
+ unique-slug@2.0.2:
+ dependencies:
+ imurmurhash: 0.1.4
+ optional: true
+
+ universalify@2.0.1: {}
+
+ unpipe@1.0.0: {}
+
+ url-template@2.0.8: {}
+
+ util-deprecate@1.0.2: {}
+
+ uuid@10.0.0: {}
+
+ uuid@11.1.0: {}
+
+ uuid@13.0.0: {}
+
+ uuid@8.3.2: {}
+
+ uuid@9.0.1: {}
+
+ vary@1.1.2: {}
+
+ vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.4):
+ dependencies:
+ lightningcss: 1.32.0
+ picomatch: 4.0.4
+ postcss: 8.5.8
+ rolldown: 1.0.0-rc.12(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ '@types/node': 24.12.0
+ esbuild: 0.27.4
+ fsevents: 2.3.3
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+
+ vitest@4.1.2(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.4)):
+ dependencies:
+ '@vitest/expect': 4.1.2
+ '@vitest/mocker': 4.1.2(vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.4))
+ '@vitest/pretty-format': 4.1.2
+ '@vitest/runner': 4.1.2
+ '@vitest/snapshot': 4.1.2
+ '@vitest/spy': 4.1.2
+ '@vitest/utils': 4.1.2
+ es-module-lexer: 2.0.0
+ expect-type: 1.3.0
+ magic-string: 0.30.21
+ obug: 2.1.1
+ pathe: 2.0.3
+ picomatch: 4.0.4
+ std-env: 4.0.0
+ tinybench: 2.9.0
+ tinyexec: 1.0.4
+ tinyglobby: 0.2.15
+ tinyrainbow: 3.1.0
+ vite: 8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@24.12.0)(esbuild@0.27.4)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@opentelemetry/api': 1.9.1
+ '@types/node': 24.12.0
+ transitivePeerDependencies:
+ - msw
+
+ web-streams-polyfill@3.3.3: {}
+
+ web-tree-sitter@0.24.7: {}
+
+ webidl-conversions@3.0.1: {}
+
+ whatwg-url@5.0.0:
+ dependencies:
+ tr46: 0.0.3
+ webidl-conversions: 3.0.1
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ why-is-node-running@2.3.0:
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
+
+ wide-align@1.1.5:
+ dependencies:
+ string-width: 4.2.3
+ optional: true
+
+ winston-transport@4.9.0:
+ dependencies:
+ logform: 2.7.0
+ readable-stream: 3.6.2
+ triple-beam: 1.4.1
+
+ winston@3.19.0:
+ dependencies:
+ '@colors/colors': 1.6.0
+ '@dabh/diagnostics': 2.0.8
+ async: 3.2.6
+ is-stream: 2.0.1
+ logform: 2.7.0
+ one-time: 1.0.0
+ readable-stream: 3.6.2
+ safe-stable-stringify: 2.5.0
+ stack-trace: 0.0.10
+ triple-beam: 1.4.1
+ winston-transport: 4.9.0
+
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrappy@1.0.2: {}
+
+ ws@8.20.0: {}
+
+ wsl-utils@0.1.0:
+ dependencies:
+ is-wsl: 3.1.1
+
+ xtend@4.0.2: {}
+
+ y18n@5.0.8: {}
+
+ yallist@4.0.0: {}
+
+ yargs-parser@21.1.1: {}
+
+ yargs@17.7.2:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
+
+ yocto-queue@0.1.0: {}
+
+ zod-to-json-schema@3.25.2(zod@4.3.6):
+ dependencies:
+ zod: 4.3.6
+
+ zod@4.1.8: {}
+
+ zod@4.3.6: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 0000000..029f546
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,3 @@
+packages:
+ - 'js'
+ - 'plugins/*'
diff --git a/proto/sigil/v1/generation_ingest.proto b/proto/sigil/v1/generation_ingest.proto
index 733a27f..fd7355d 100644
--- a/proto/sigil/v1/generation_ingest.proto
+++ b/proto/sigil/v1/generation_ingest.proto
@@ -83,6 +83,7 @@ message ToolDefinition {
string description = 2;
string type = 3;
bytes input_schema_json = 4;
+ bool deferred = 5;
}
message TokenUsage {
@@ -92,6 +93,7 @@ message TokenUsage {
int64 cache_read_input_tokens = 4;
int64 cache_write_input_tokens = 5;
int64 reasoning_tokens = 6;
+ int64 cache_creation_input_tokens = 7;
}
enum ArtifactKind {
diff --git a/python-frameworks/google-adk/LICENSE b/python-frameworks/google-adk/LICENSE
index ae8c60c..626a3ab 100644
--- a/python-frameworks/google-adk/LICENSE
+++ b/python-frameworks/google-adk/LICENSE
@@ -1,3 +1,3 @@
SPDX-License-Identifier: Apache-2.0
-See /sdks/LICENSE at repository root for full license text.
+See /LICENSE at repository root for full license text.
diff --git a/python-frameworks/google-adk/pyproject.toml b/python-frameworks/google-adk/pyproject.toml
index 6dcf6f2..ba7a5a4 100644
--- a/python-frameworks/google-adk/pyproject.toml
+++ b/python-frameworks/google-adk/pyproject.toml
@@ -1,12 +1,12 @@
[project]
name = "sigil-sdk-google-adk"
-version = "0.1.0"
+version = "0.1.2"
description = "Google ADK callback handlers for Sigil Python SDK"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.10"
dependencies = [
- "sigil-sdk>=0.1.0",
+ "sigil-sdk>=0.1.2",
"google-adk>=1.0.0",
]
diff --git a/python-frameworks/google-adk/tests/test_sigil_sdk_google_adk.py b/python-frameworks/google-adk/tests/test_sigil_sdk_google_adk.py
index 460420e..9f91ba5 100644
--- a/python-frameworks/google-adk/tests/test_sigil_sdk_google_adk.py
+++ b/python-frameworks/google-adk/tests/test_sigil_sdk_google_adk.py
@@ -7,6 +7,9 @@
from uuid import UUID, uuid4
from google.adk.plugins import BasePlugin
+from opentelemetry.sdk.trace import TracerProvider
+from opentelemetry.sdk.trace.export import SimpleSpanProcessor
+from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
from sigil_sdk import Client, ClientConfig, GenerationExportConfig
from sigil_sdk.models import ExportGenerationResult, ExportGenerationsResponse
from sigil_sdk_google_adk import (
@@ -38,9 +41,10 @@ def shutdown(self) -> None:
return
-def _new_client(exporter: _CapturingExporter) -> Client:
+def _new_client(exporter: _CapturingExporter, tracer=None) -> Client:
return Client(
ClientConfig(
+ tracer=tracer,
generation_export=GenerationExportConfig(batch_size=10, flush_interval=timedelta(seconds=60)),
generation_exporter=exporter,
)
@@ -183,6 +187,47 @@ def test_sigil_sdk_google_adk_stream_mode_uses_chunks_when_output_missing() -> N
client.shutdown()
+def test_sigil_sdk_google_adk_generation_span_tracks_active_parent_span_and_export_lineage() -> None:
+ exporter = _CapturingExporter()
+ span_exporter = InMemorySpanExporter()
+ provider = TracerProvider()
+ provider.add_span_processor(SimpleSpanProcessor(span_exporter))
+ tracer = provider.get_tracer("sigil-framework-test")
+ client = _new_client(exporter, tracer=tracer)
+
+ try:
+ run_id = uuid4()
+ with tracer.start_as_current_span("framework.request"):
+ handler = SigilGoogleAdkHandler(client=client, provider_resolver="auto")
+ handler.on_chat_model_start(
+ {"name": "ChatModel"},
+ [[{"type": "human", "content": "hello"}]],
+ run_id=run_id,
+ parent_run_id=uuid4(),
+ invocation_params={"model": "gpt-5"},
+ metadata={"conversation_id": "framework-conversation-lineage-42", "thread_id": "framework-thread-lineage-42"},
+ )
+ handler.on_llm_end(
+ {"generations": [[{"text": "world"}]], "llm_output": {"model_name": "gpt-5", "finish_reason": "stop"}},
+ run_id=run_id,
+ )
+
+ client.flush()
+ generation = exporter.requests[0].generations[0]
+ spans = span_exporter.get_finished_spans()
+ parent_span = next(span for span in spans if span.name == "framework.request")
+ generation_span = next(span for span in spans if span.attributes.get("gen_ai.operation.name") == "generateText")
+
+ assert generation_span.parent is not None
+ assert generation_span.parent.span_id == parent_span.context.span_id
+ assert generation_span.context.trace_id == parent_span.context.trace_id
+ assert generation.trace_id == generation_span.context.trace_id.to_bytes(16, "big").hex()
+ assert generation.span_id == generation_span.context.span_id.to_bytes(8, "big").hex()
+ finally:
+ client.shutdown()
+ provider.shutdown()
+
+
def test_sigil_sdk_google_adk_normalizes_extra_metadata() -> None:
exporter = _CapturingExporter()
client = _new_client(exporter)
@@ -333,6 +378,18 @@ async def _run() -> None:
client.shutdown()
+def test_sigil_sdk_google_adk_handler_explicitly_has_no_embedding_lifecycle() -> None:
+ exporter = _CapturingExporter()
+ client = _new_client(exporter)
+ try:
+ handler = SigilGoogleAdkHandler(client=client)
+ assert not hasattr(handler, "on_embedding_start")
+ assert not hasattr(handler, "on_embedding_end")
+ assert not hasattr(handler, "on_embedding_error")
+ finally:
+ client.shutdown()
+
+
def test_sigil_sdk_google_adk_callbacks_close_tool_runs_without_function_call_id() -> None:
class _CapturingHandler:
def __init__(self) -> None:
diff --git a/python-frameworks/langchain/LICENSE b/python-frameworks/langchain/LICENSE
index ae8c60c..626a3ab 100644
--- a/python-frameworks/langchain/LICENSE
+++ b/python-frameworks/langchain/LICENSE
@@ -1,3 +1,3 @@
SPDX-License-Identifier: Apache-2.0
-See /sdks/LICENSE at repository root for full license text.
+See /LICENSE at repository root for full license text.
diff --git a/python-frameworks/langchain/pyproject.toml b/python-frameworks/langchain/pyproject.toml
index 110f282..be11da9 100644
--- a/python-frameworks/langchain/pyproject.toml
+++ b/python-frameworks/langchain/pyproject.toml
@@ -1,12 +1,12 @@
[project]
name = "sigil-sdk-langchain"
-version = "0.1.0"
+version = "0.1.2"
description = "LangChain callback handlers for Sigil Python SDK"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.10"
dependencies = [
- "sigil-sdk>=0.1.0",
+ "sigil-sdk>=0.1.2",
"langchain-core>=0.3.0",
]
diff --git a/python-frameworks/langchain/sigil_sdk_langchain/handler.py b/python-frameworks/langchain/sigil_sdk_langchain/handler.py
index d7f0eba..8bbd867 100644
--- a/python-frameworks/langchain/sigil_sdk_langchain/handler.py
+++ b/python-frameworks/langchain/sigil_sdk_langchain/handler.py
@@ -18,6 +18,23 @@ class AsyncCallbackHandler: # type: ignore[no-redef]
"""Fallback async base class when langchain-core is unavailable."""
+def _extract_tool_output(output: Any) -> Any:
+ """Extract serializable content from LangChain tool output.
+
+ LangChain's on_tool_end receives the raw output which may be a
+ ToolMessage or other BaseMessage subclass that isn't directly JSON
+ serializable. Extract the .content string when available.
+ """
+ if output is None:
+ return output
+ if isinstance(output, str):
+ return output
+ # Handle LangChain BaseMessage subclasses (ToolMessage, AIMessage, etc.)
+ if hasattr(output, "content"):
+ return output.content
+ return output
+
+
_framework_name = "langchain"
_framework_source = "handler"
_framework_language = "python"
@@ -149,7 +166,7 @@ def on_tool_start(
)
def on_tool_end(self, output: Any, *, run_id: UUID, **_kwargs: Any) -> None:
- self._on_tool_end(output=output, run_id=run_id)
+ self._on_tool_end(output=_extract_tool_output(output), run_id=run_id)
def on_tool_error(self, error: BaseException, *, run_id: UUID, **_kwargs: Any) -> None:
self._on_tool_error(error=error, run_id=run_id)
@@ -284,7 +301,7 @@ async def on_tool_start(
)
async def on_tool_end(self, output: Any, *, run_id: UUID, **_kwargs: Any) -> None:
- self._on_tool_end(output=output, run_id=run_id)
+ self._on_tool_end(output=_extract_tool_output(output), run_id=run_id)
async def on_tool_error(self, error: BaseException, *, run_id: UUID, **_kwargs: Any) -> None:
self._on_tool_error(error=error, run_id=run_id)
diff --git a/python-frameworks/langchain/tests/test_langchain_handler.py b/python-frameworks/langchain/tests/test_langchain_handler.py
index 04bae49..5af3b89 100644
--- a/python-frameworks/langchain/tests/test_langchain_handler.py
+++ b/python-frameworks/langchain/tests/test_langchain_handler.py
@@ -18,6 +18,7 @@
create_sigil_langchain_handler,
with_sigil_langchain_callbacks,
)
+from sigil_sdk_langchain.handler import _extract_tool_output
class _CapturingExporter:
@@ -115,6 +116,56 @@ def test_langchain_sync_lifecycle_sets_framework_tags_and_metadata() -> None:
client.shutdown()
+def test_langchain_sync_lifecycle_extracts_anthropic_style_usage_and_stop_reason() -> None:
+ """ChatAnthropic puts token usage under 'usage' (not 'token_usage') and
+ stop reason under 'stop_reason' (not 'finish_reason')."""
+ exporter = _CapturingExporter()
+ client = _new_client(exporter)
+
+ try:
+ run_id = uuid4()
+ handler = SigilLangChainHandler(
+ client=client,
+ agent_name="agent-langchain",
+ agent_version="v1",
+ provider_resolver="auto",
+ )
+
+ handler.on_chat_model_start(
+ {"name": "ChatAnthropic"},
+ [[{"type": "human", "content": "hello"}]],
+ run_id=run_id,
+ invocation_params={"model": "claude-haiku-4-5-20251001"},
+ )
+ handler.on_llm_end(
+ {
+ "generations": [[{"text": "world"}]],
+ "llm_output": {
+ "id": "msg_01ABC",
+ "model": "claude-haiku-4-5-20251001",
+ "model_name": "claude-haiku-4-5-20251001",
+ "stop_reason": "end_turn",
+ "usage": {
+ "input_tokens": 42,
+ "output_tokens": 17,
+ },
+ },
+ },
+ run_id=run_id,
+ )
+
+ client.flush()
+ generation = exporter.requests[0].generations[0]
+ assert generation.model.provider == "anthropic"
+ assert generation.model.name == "claude-haiku-4-5-20251001"
+ assert generation.usage.input_tokens == 42
+ assert generation.usage.output_tokens == 17
+ assert generation.usage.total_tokens == 59
+ assert generation.stop_reason == "end_turn"
+ finally:
+ client.shutdown()
+
+
def test_langchain_stream_lifecycle_uses_stream_mode_and_chunk_fallback() -> None:
exporter = _CapturingExporter()
client = _new_client(exporter)
@@ -175,6 +226,47 @@ def _tracking_set_first_token_at(self, first_token_at):
client.shutdown()
+def test_langchain_generation_span_tracks_active_parent_span_and_export_lineage() -> None:
+ exporter = _CapturingExporter()
+ span_exporter = InMemorySpanExporter()
+ provider = TracerProvider()
+ provider.add_span_processor(SimpleSpanProcessor(span_exporter))
+ tracer = provider.get_tracer("sigil-framework-test")
+ client = _new_client(exporter, tracer=tracer)
+
+ try:
+ run_id = uuid4()
+ with tracer.start_as_current_span("framework.request"):
+ handler = SigilLangChainHandler(client=client)
+ handler.on_chat_model_start(
+ {"name": "ChatOpenAI"},
+ [[{"type": "human", "content": "hello"}]],
+ run_id=run_id,
+ parent_run_id=uuid4(),
+ invocation_params={"model": "gpt-5"},
+ metadata={"thread_id": "chain-thread-lineage-42"},
+ )
+ handler.on_llm_end(
+ {"generations": [[{"text": "world"}]], "llm_output": {"model_name": "gpt-5", "finish_reason": "stop"}},
+ run_id=run_id,
+ )
+
+ client.flush()
+ generation = exporter.requests[0].generations[0]
+ spans = span_exporter.get_finished_spans()
+ parent_span = next(span for span in spans if span.name == "framework.request")
+ generation_span = next(span for span in spans if span.attributes.get("gen_ai.operation.name") == "generateText")
+
+ assert generation_span.parent is not None
+ assert generation_span.parent.span_id == parent_span.context.span_id
+ assert generation_span.context.trace_id == parent_span.context.trace_id
+ assert generation.trace_id == generation_span.context.trace_id.to_bytes(16, "big").hex()
+ assert generation.span_id == generation_span.context.span_id.to_bytes(8, "big").hex()
+ finally:
+ client.shutdown()
+ provider.shutdown()
+
+
def test_langchain_provider_resolution_supports_known_models_and_fallback() -> None:
exporter = _CapturingExporter()
client = _new_client(exporter)
@@ -244,6 +336,45 @@ async def _run() -> None:
client.shutdown()
+def test_extract_tool_output_unwraps_message_content_and_preserves_plain_values() -> None:
+ class _FakeToolMessage:
+ def __init__(self, content):
+ self.content = content
+
+ payload = {"temp_c": 18}
+
+ assert _extract_tool_output(_FakeToolMessage("tool result text")) == "tool result text"
+ assert _extract_tool_output("plain string") == "plain string"
+ assert _extract_tool_output(None) is None
+ assert _extract_tool_output(payload) is payload
+
+
+def test_langchain_tool_end_extracts_message_content_before_recording() -> None:
+ exporter = _CapturingExporter()
+ client = _new_client(exporter)
+
+ class _FakeToolMessage:
+ def __init__(self, content):
+ self.content = content
+
+ try:
+ handler = SigilLangChainHandler(client=client)
+ captured: dict[str, object] = {}
+
+ def _capture_tool_end(*, output, run_id) -> None:
+ captured["output"] = output
+ captured["run_id"] = run_id
+
+ handler._on_tool_end = _capture_tool_end # type: ignore[method-assign]
+
+ run_id = uuid4()
+ handler.on_tool_end(_FakeToolMessage("tool result text"), run_id=run_id)
+
+ assert captured == {"output": "tool result text", "run_id": run_id}
+ finally:
+ client.shutdown()
+
+
def test_langchain_tool_chain_and_retriever_callbacks_emit_spans() -> None:
exporter = _CapturingExporter()
span_exporter = InMemorySpanExporter()
@@ -333,6 +464,18 @@ def test_langchain_attach_helpers_preserve_existing_callbacks() -> None:
client.shutdown()
+def test_langchain_handler_explicitly_has_no_embedding_lifecycle() -> None:
+ exporter = _CapturingExporter()
+ client = _new_client(exporter)
+ try:
+ handler = SigilLangChainHandler(client=client)
+ assert not hasattr(handler, "on_embedding_start")
+ assert not hasattr(handler, "on_embedding_end")
+ assert not hasattr(handler, "on_embedding_error")
+ finally:
+ client.shutdown()
+
+
def test_langchain_attach_helpers_do_not_duplicate_existing_sigil_handler() -> None:
exporter = _CapturingExporter()
client = _new_client(exporter)
diff --git a/python-frameworks/langgraph/LICENSE b/python-frameworks/langgraph/LICENSE
index ae8c60c..626a3ab 100644
--- a/python-frameworks/langgraph/LICENSE
+++ b/python-frameworks/langgraph/LICENSE
@@ -1,3 +1,3 @@
SPDX-License-Identifier: Apache-2.0
-See /sdks/LICENSE at repository root for full license text.
+See /LICENSE at repository root for full license text.
diff --git a/python-frameworks/langgraph/pyproject.toml b/python-frameworks/langgraph/pyproject.toml
index a47c530..45e0b25 100644
--- a/python-frameworks/langgraph/pyproject.toml
+++ b/python-frameworks/langgraph/pyproject.toml
@@ -1,12 +1,12 @@
[project]
name = "sigil-sdk-langgraph"
-version = "0.1.0"
+version = "0.1.2"
description = "LangGraph callback handlers for Sigil Python SDK"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.10"
dependencies = [
- "sigil-sdk>=0.1.0",
+ "sigil-sdk>=0.1.2",
"langchain-core>=0.3.0",
"langgraph>=0.2.0",
]
diff --git a/python-frameworks/langgraph/tests/test_langgraph_handler.py b/python-frameworks/langgraph/tests/test_langgraph_handler.py
index ba8d793..fb1c703 100644
--- a/python-frameworks/langgraph/tests/test_langgraph_handler.py
+++ b/python-frameworks/langgraph/tests/test_langgraph_handler.py
@@ -142,6 +142,47 @@ def test_langgraph_stream_lifecycle_uses_stream_mode_and_chunk_fallback() -> Non
client.shutdown()
+def test_langgraph_generation_span_tracks_active_parent_span_and_export_lineage() -> None:
+ exporter = _CapturingExporter()
+ span_exporter = InMemorySpanExporter()
+ provider = TracerProvider()
+ provider.add_span_processor(SimpleSpanProcessor(span_exporter))
+ tracer = provider.get_tracer("sigil-framework-test")
+ client = _new_client(exporter, tracer=tracer)
+
+ try:
+ run_id = uuid4()
+ with tracer.start_as_current_span("framework.request"):
+ handler = SigilLangGraphHandler(client=client)
+ handler.on_chat_model_start(
+ {"name": "ChatOpenAI"},
+ [[{"type": "human", "content": "hello"}]],
+ run_id=run_id,
+ parent_run_id=uuid4(),
+ invocation_params={"model": "gpt-5"},
+ metadata={"thread_id": "graph-thread-lineage-42", "langgraph_node": "answer_node"},
+ )
+ handler.on_llm_end(
+ {"generations": [[{"text": "world"}]], "llm_output": {"model_name": "gpt-5", "finish_reason": "stop"}},
+ run_id=run_id,
+ )
+
+ client.flush()
+ generation = exporter.requests[0].generations[0]
+ spans = span_exporter.get_finished_spans()
+ parent_span = next(span for span in spans if span.name == "framework.request")
+ generation_span = next(span for span in spans if span.attributes.get("gen_ai.operation.name") == "generateText")
+
+ assert generation_span.parent is not None
+ assert generation_span.parent.span_id == parent_span.context.span_id
+ assert generation_span.context.trace_id == parent_span.context.trace_id
+ assert generation.trace_id == generation_span.context.trace_id.to_bytes(16, "big").hex()
+ assert generation.span_id == generation_span.context.span_id.to_bytes(8, "big").hex()
+ finally:
+ client.shutdown()
+ provider.shutdown()
+
+
def test_langgraph_provider_resolution_supports_known_models_and_fallback() -> None:
exporter = _CapturingExporter()
client = _new_client(exporter)
@@ -299,3 +340,15 @@ def test_langgraph_attach_helpers_preserve_existing_callbacks() -> None:
assert isinstance(callbacks[1], SigilLangGraphHandler)
finally:
client.shutdown()
+
+
+def test_langgraph_handler_explicitly_has_no_embedding_lifecycle() -> None:
+ exporter = _CapturingExporter()
+ client = _new_client(exporter)
+ try:
+ handler = SigilLangGraphHandler(client=client)
+ assert not hasattr(handler, "on_embedding_start")
+ assert not hasattr(handler, "on_embedding_end")
+ assert not hasattr(handler, "on_embedding_error")
+ finally:
+ client.shutdown()
diff --git a/python-frameworks/llamaindex/LICENSE b/python-frameworks/llamaindex/LICENSE
index ae8c60c..626a3ab 100644
--- a/python-frameworks/llamaindex/LICENSE
+++ b/python-frameworks/llamaindex/LICENSE
@@ -1,3 +1,3 @@
SPDX-License-Identifier: Apache-2.0
-See /sdks/LICENSE at repository root for full license text.
+See /LICENSE at repository root for full license text.
diff --git a/python-frameworks/llamaindex/pyproject.toml b/python-frameworks/llamaindex/pyproject.toml
index ed38814..066bd64 100644
--- a/python-frameworks/llamaindex/pyproject.toml
+++ b/python-frameworks/llamaindex/pyproject.toml
@@ -1,12 +1,12 @@
[project]
name = "sigil-sdk-llamaindex"
-version = "0.1.0"
+version = "0.1.2"
description = "LlamaIndex callback handlers for Sigil Python SDK"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.10"
dependencies = [
- "sigil-sdk>=0.1.0",
+ "sigil-sdk>=0.1.2",
"llama-index>=0.14.0",
]
diff --git a/python-frameworks/llamaindex/tests/test_sigil_sdk_llamaindex.py b/python-frameworks/llamaindex/tests/test_sigil_sdk_llamaindex.py
index 350fea8..6fccab0 100644
--- a/python-frameworks/llamaindex/tests/test_sigil_sdk_llamaindex.py
+++ b/python-frameworks/llamaindex/tests/test_sigil_sdk_llamaindex.py
@@ -7,6 +7,9 @@
from uuid import uuid4
from llama_index.core.callbacks.base_handler import BaseCallbackHandler
+from opentelemetry.sdk.trace import TracerProvider
+from opentelemetry.sdk.trace.export import SimpleSpanProcessor
+from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
from sigil_sdk import Client, ClientConfig, GenerationExportConfig
from sigil_sdk.models import ExportGenerationResult, ExportGenerationsResponse
from sigil_sdk_llamaindex import (
@@ -35,9 +38,10 @@ def shutdown(self) -> None:
return
-def _new_client(exporter: _CapturingExporter) -> Client:
+def _new_client(exporter: _CapturingExporter, tracer=None) -> Client:
return Client(
ClientConfig(
+ tracer=tracer,
generation_export=GenerationExportConfig(batch_size=10, flush_interval=timedelta(seconds=60)),
generation_exporter=exporter,
)
@@ -180,6 +184,47 @@ def test_sigil_sdk_llamaindex_stream_mode_uses_chunks_when_output_missing() -> N
client.shutdown()
+def test_sigil_sdk_llamaindex_generation_span_tracks_active_parent_span_and_export_lineage() -> None:
+ exporter = _CapturingExporter()
+ span_exporter = InMemorySpanExporter()
+ provider = TracerProvider()
+ provider.add_span_processor(SimpleSpanProcessor(span_exporter))
+ tracer = provider.get_tracer("sigil-framework-test")
+ client = _new_client(exporter, tracer=tracer)
+
+ try:
+ run_id = uuid4()
+ with tracer.start_as_current_span("framework.request"):
+ handler = SigilLlamaIndexHandler(client=client, provider_resolver="auto")
+ handler.on_chat_model_start(
+ {"name": "ChatModel"},
+ [[{"type": "human", "content": "hello"}]],
+ run_id=run_id,
+ parent_run_id=uuid4(),
+ invocation_params={"model": "gpt-5"},
+ metadata={"conversation_id": "framework-conversation-lineage-42", "thread_id": "framework-thread-lineage-42"},
+ )
+ handler.on_llm_end(
+ {"generations": [[{"text": "world"}]], "llm_output": {"model_name": "gpt-5", "finish_reason": "stop"}},
+ run_id=run_id,
+ )
+
+ client.flush()
+ generation = exporter.requests[0].generations[0]
+ spans = span_exporter.get_finished_spans()
+ parent_span = next(span for span in spans if span.name == "framework.request")
+ generation_span = next(span for span in spans if span.attributes.get("gen_ai.operation.name") == "generateText")
+
+ assert generation_span.parent is not None
+ assert generation_span.parent.span_id == parent_span.context.span_id
+ assert generation_span.context.trace_id == parent_span.context.trace_id
+ assert generation.trace_id == generation_span.context.trace_id.to_bytes(16, "big").hex()
+ assert generation.span_id == generation_span.context.span_id.to_bytes(8, "big").hex()
+ finally:
+ client.shutdown()
+ provider.shutdown()
+
+
def test_sigil_sdk_llamaindex_normalizes_extra_metadata() -> None:
exporter = _CapturingExporter()
client = _new_client(exporter)
@@ -306,3 +351,15 @@ def end_trace(self, *_args, **_kwargs) -> None:
assert generation.stop_reason == "stop"
finally:
client.shutdown()
+
+
+def test_sigil_sdk_llamaindex_handler_explicitly_has_no_embedding_lifecycle() -> None:
+ exporter = _CapturingExporter()
+ client = _new_client(exporter)
+ try:
+ handler = SigilLlamaIndexHandler(client=client)
+ assert not hasattr(handler, "on_embedding_start")
+ assert not hasattr(handler, "on_embedding_end")
+ assert not hasattr(handler, "on_embedding_error")
+ finally:
+ client.shutdown()
diff --git a/python-frameworks/openai-agents/LICENSE b/python-frameworks/openai-agents/LICENSE
index ae8c60c..626a3ab 100644
--- a/python-frameworks/openai-agents/LICENSE
+++ b/python-frameworks/openai-agents/LICENSE
@@ -1,3 +1,3 @@
SPDX-License-Identifier: Apache-2.0
-See /sdks/LICENSE at repository root for full license text.
+See /LICENSE at repository root for full license text.
diff --git a/python-frameworks/openai-agents/pyproject.toml b/python-frameworks/openai-agents/pyproject.toml
index c322874..51f6163 100644
--- a/python-frameworks/openai-agents/pyproject.toml
+++ b/python-frameworks/openai-agents/pyproject.toml
@@ -1,12 +1,12 @@
[project]
name = "sigil-sdk-openai-agents"
-version = "0.1.0"
+version = "0.1.2"
description = "OpenAI Agents callback handlers for Sigil Python SDK"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.10"
dependencies = [
- "sigil-sdk>=0.1.0",
+ "sigil-sdk>=0.1.2",
"openai-agents>=0.9.0",
]
diff --git a/python-frameworks/openai-agents/tests/test_sigil_sdk_openai_agents.py b/python-frameworks/openai-agents/tests/test_sigil_sdk_openai_agents.py
index 54e285d..5754f41 100644
--- a/python-frameworks/openai-agents/tests/test_sigil_sdk_openai_agents.py
+++ b/python-frameworks/openai-agents/tests/test_sigil_sdk_openai_agents.py
@@ -7,6 +7,9 @@
from uuid import uuid4
import pytest
+from opentelemetry.sdk.trace import TracerProvider
+from opentelemetry.sdk.trace.export import SimpleSpanProcessor
+from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
from agents import RunHooks
from sigil_sdk import Client, ClientConfig, GenerationExportConfig
from sigil_sdk.models import ExportGenerationResult, ExportGenerationsResponse
@@ -36,9 +39,10 @@ def shutdown(self) -> None:
return
-def _new_client(exporter: _CapturingExporter) -> Client:
+def _new_client(exporter: _CapturingExporter, tracer=None) -> Client:
return Client(
ClientConfig(
+ tracer=tracer,
generation_export=GenerationExportConfig(batch_size=10, flush_interval=timedelta(seconds=60)),
generation_exporter=exporter,
)
@@ -181,6 +185,47 @@ def test_sigil_sdk_openai_agents_stream_mode_uses_chunks_when_output_missing() -
client.shutdown()
+def test_sigil_sdk_openai_agents_generation_span_tracks_active_parent_span_and_export_lineage() -> None:
+ exporter = _CapturingExporter()
+ span_exporter = InMemorySpanExporter()
+ provider = TracerProvider()
+ provider.add_span_processor(SimpleSpanProcessor(span_exporter))
+ tracer = provider.get_tracer("sigil-framework-test")
+ client = _new_client(exporter, tracer=tracer)
+
+ try:
+ run_id = uuid4()
+ with tracer.start_as_current_span("framework.request"):
+ handler = SigilOpenAIAgentsHandler(client=client, provider_resolver="auto")
+ handler.on_chat_model_start(
+ {"name": "ChatModel"},
+ [[{"type": "human", "content": "hello"}]],
+ run_id=run_id,
+ parent_run_id=uuid4(),
+ invocation_params={"model": "gpt-5"},
+ metadata={"conversation_id": "framework-conversation-lineage-42", "thread_id": "framework-thread-lineage-42"},
+ )
+ handler.on_llm_end(
+ {"generations": [[{"text": "world"}]], "llm_output": {"model_name": "gpt-5", "finish_reason": "stop"}},
+ run_id=run_id,
+ )
+
+ client.flush()
+ generation = exporter.requests[0].generations[0]
+ spans = span_exporter.get_finished_spans()
+ parent_span = next(span for span in spans if span.name == "framework.request")
+ generation_span = next(span for span in spans if span.attributes.get("gen_ai.operation.name") == "generateText")
+
+ assert generation_span.parent is not None
+ assert generation_span.parent.span_id == parent_span.context.span_id
+ assert generation_span.context.trace_id == parent_span.context.trace_id
+ assert generation.trace_id == generation_span.context.trace_id.to_bytes(16, "big").hex()
+ assert generation.span_id == generation_span.context.span_id.to_bytes(8, "big").hex()
+ finally:
+ client.shutdown()
+ provider.shutdown()
+
+
def test_sigil_sdk_openai_agents_normalizes_extra_metadata() -> None:
exporter = _CapturingExporter()
client = _new_client(exporter)
@@ -299,3 +344,15 @@ async def _run() -> None:
with_sigil_openai_agents_hooks({"hooks": [existing]}, client=client)
finally:
client.shutdown()
+
+
+def test_sigil_sdk_openai_agents_handler_explicitly_has_no_embedding_lifecycle() -> None:
+ exporter = _CapturingExporter()
+ client = _new_client(exporter)
+ try:
+ handler = SigilOpenAIAgentsHandler(client=client)
+ assert not hasattr(handler, "on_embedding_start")
+ assert not hasattr(handler, "on_embedding_end")
+ assert not hasattr(handler, "on_embedding_error")
+ finally:
+ client.shutdown()
diff --git a/python-providers/LICENSE b/python-providers/LICENSE
index ae8c60c..626a3ab 100644
--- a/python-providers/LICENSE
+++ b/python-providers/LICENSE
@@ -1,3 +1,3 @@
SPDX-License-Identifier: Apache-2.0
-See /sdks/LICENSE at repository root for full license text.
+See /LICENSE at repository root for full license text.
diff --git a/python-providers/anthropic/LICENSE b/python-providers/anthropic/LICENSE
index ae8c60c..626a3ab 100644
--- a/python-providers/anthropic/LICENSE
+++ b/python-providers/anthropic/LICENSE
@@ -1,3 +1,3 @@
SPDX-License-Identifier: Apache-2.0
-See /sdks/LICENSE at repository root for full license text.
+See /LICENSE at repository root for full license text.
diff --git a/python-providers/anthropic/pyproject.toml b/python-providers/anthropic/pyproject.toml
index a5e1997..088470e 100644
--- a/python-providers/anthropic/pyproject.toml
+++ b/python-providers/anthropic/pyproject.toml
@@ -1,12 +1,12 @@
[project]
name = "sigil-sdk-anthropic"
-version = "0.1.0"
+version = "0.1.2"
description = "Anthropic helper wrappers for Sigil Python SDK"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.10"
dependencies = [
- "sigil-sdk>=0.1.0",
+ "sigil-sdk>=0.1.2",
"anthropic>=0.79.0,<1",
]
diff --git a/python-providers/anthropic/tests/test_anthropic_provider.py b/python-providers/anthropic/tests/test_anthropic_provider.py
index d3b5fb2..59477cf 100644
--- a/python-providers/anthropic/tests/test_anthropic_provider.py
+++ b/python-providers/anthropic/tests/test_anthropic_provider.py
@@ -8,6 +8,7 @@
from sigil_sdk import Client, ClientConfig, GenerationExportConfig
from sigil_sdk.models import ExportGenerationResult, ExportGenerationsResponse
+import sigil_sdk_anthropic
from sigil_sdk_anthropic import AnthropicOptions, AnthropicStreamSummary, messages
@@ -145,6 +146,45 @@ def test_anthropic_wrapper_propagates_provider_error_and_sets_call_error() -> No
client.shutdown()
+def test_anthropic_wrappers_tolerate_missing_provider_payload_fields() -> None:
+ exporter = _CapturingExporter()
+ client = _new_client(exporter)
+ try:
+ messages.create(
+ client,
+ _request(),
+ lambda _request: {
+ "id": "resp-malformed",
+ "model": "claude-sonnet-4-5-20260210",
+ "role": "assistant",
+ "content": [],
+ },
+ )
+ messages.stream(
+ client,
+ _request(),
+ lambda _request: AnthropicStreamSummary(events=[{"type": "content_block_delta", "delta": {"type": "text_delta"}}]),
+ )
+
+ client.flush()
+ generations = exporter.requests[0].generations
+ assert len(generations) == 2
+
+ sync_generation = generations[0]
+ assert sync_generation.mode.value == "SYNC"
+ assert sync_generation.response_id == "resp-malformed"
+ assert sync_generation.response_model == "claude-sonnet-4-5-20260210"
+ assert sync_generation.output == []
+ assert sync_generation.stop_reason == ""
+
+ stream_generation = generations[1]
+ assert stream_generation.mode.value == "STREAM"
+ assert stream_generation.response_model == "claude-sonnet-4-5"
+ assert stream_generation.output == []
+ finally:
+ client.shutdown()
+
+
def test_anthropic_mappers_use_strict_payloads_and_support_raw_artifacts() -> None:
request = _request()
response = _response()
@@ -200,3 +240,9 @@ def test_anthropic_mapper_maps_thinking_disabled() -> None:
mapped = messages.from_request_response(request, response)
assert mapped.thinking_enabled is False
+
+
+def test_anthropic_provider_explicitly_has_no_embeddings_surface() -> None:
+ assert "messages" in sigil_sdk_anthropic.__all__
+ assert "embeddings" not in sigil_sdk_anthropic.__all__
+ assert not hasattr(sigil_sdk_anthropic, "embeddings")
diff --git a/python-providers/gemini/LICENSE b/python-providers/gemini/LICENSE
index ae8c60c..626a3ab 100644
--- a/python-providers/gemini/LICENSE
+++ b/python-providers/gemini/LICENSE
@@ -1,3 +1,3 @@
SPDX-License-Identifier: Apache-2.0
-See /sdks/LICENSE at repository root for full license text.
+See /LICENSE at repository root for full license text.
diff --git a/python-providers/gemini/README.md b/python-providers/gemini/README.md
index d1c8714..a1790af 100644
--- a/python-providers/gemini/README.md
+++ b/python-providers/gemini/README.md
@@ -8,6 +8,20 @@
pip install sigil-sdk sigil-sdk-gemini google-genai
```
+## Public API
+
+- Wrappers:
+ - `models.generate_content(...)`
+ - `models.generate_content_async(...)`
+ - `models.generate_content_stream(...)`
+ - `models.generate_content_stream_async(...)`
+ - `models.embed_content(...)`
+ - `models.embed_content_async(...)`
+- Mappers:
+ - `models.from_request_response(...)`
+ - `models.from_stream(...)`
+ - `models.embedding_from_response(...)`
+
## Wrapper Mode (Sync)
```python
@@ -62,6 +76,22 @@ generation = models.from_request_response(model, contents, config, response)
stream_generation = models.from_stream(model, contents, config, summary)
```
+## Embedding example
+
+```python
+embedding_response = models.embed_content(
+ client,
+ "gemini-embedding-001",
+ contents,
+ None,
+ lambda req_model, req_contents, req_config: gemini_client.models.embed_content(
+ model=req_model,
+ contents=req_contents,
+ config=req_config,
+ ),
+)
+```
+
## Raw Provider Artifacts (Opt-In)
```python
diff --git a/python-providers/gemini/pyproject.toml b/python-providers/gemini/pyproject.toml
index b5f21b7..ab65cf0 100644
--- a/python-providers/gemini/pyproject.toml
+++ b/python-providers/gemini/pyproject.toml
@@ -1,12 +1,12 @@
[project]
name = "sigil-sdk-gemini"
-version = "0.1.0"
+version = "0.1.2"
description = "Gemini helper wrappers for Sigil Python SDK"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.10"
dependencies = [
- "sigil-sdk>=0.1.0",
+ "sigil-sdk>=0.1.2",
"google-genai>=1.63.0,<2",
]
diff --git a/python-providers/gemini/tests/test_gemini_provider.py b/python-providers/gemini/tests/test_gemini_provider.py
index f878346..7113cf4 100644
--- a/python-providers/gemini/tests/test_gemini_provider.py
+++ b/python-providers/gemini/tests/test_gemini_provider.py
@@ -195,6 +195,48 @@ def test_gemini_wrapper_propagates_provider_error_and_sets_call_error() -> None:
client.shutdown()
+def test_gemini_wrappers_tolerate_missing_provider_payload_fields() -> None:
+ exporter = _CapturingExporter()
+ client = _new_client(exporter)
+ try:
+ models.generate_content(
+ client,
+ "gemini-2.5-pro",
+ _contents(),
+ _config(),
+ lambda _model, _contents, _config: {
+ "response_id": "resp-malformed",
+ "model_version": "gemini-2.5-pro-001",
+ "candidates": [],
+ },
+ )
+ models.generate_content_stream(
+ client,
+ "gemini-2.5-pro",
+ _contents(),
+ _config(),
+ lambda _model, _contents, _config: GeminiStreamSummary(responses=[{"model_version": "gemini-2.5-pro-001"}]),
+ )
+
+ client.flush()
+ generations = exporter.requests[0].generations
+ assert len(generations) == 2
+
+ sync_generation = generations[0]
+ assert sync_generation.mode.value == "SYNC"
+ assert sync_generation.response_id == "resp-malformed"
+ assert sync_generation.response_model == "gemini-2.5-pro-001"
+ assert sync_generation.output == []
+ assert sync_generation.stop_reason == ""
+
+ stream_generation = generations[1]
+ assert stream_generation.mode.value == "STREAM"
+ assert stream_generation.response_model == "gemini-2.5-pro-001"
+ assert stream_generation.output == []
+ finally:
+ client.shutdown()
+
+
def test_gemini_embeddings_wrapper_records_span_and_skips_generation_export() -> None:
exporter = _CapturingExporter()
span_exporter = InMemorySpanExporter()
diff --git a/python-providers/openai/LICENSE b/python-providers/openai/LICENSE
index ae8c60c..626a3ab 100644
--- a/python-providers/openai/LICENSE
+++ b/python-providers/openai/LICENSE
@@ -1,3 +1,3 @@
SPDX-License-Identifier: Apache-2.0
-See /sdks/LICENSE at repository root for full license text.
+See /LICENSE at repository root for full license text.
diff --git a/python-providers/openai/README.md b/python-providers/openai/README.md
index a8c1801..b845875 100644
--- a/python-providers/openai/README.md
+++ b/python-providers/openai/README.md
@@ -26,6 +26,11 @@ pip install sigil-sdk sigil-sdk-openai
- `responses.from_request_response(...)`
- `responses.from_stream(...)`
+- Embeddings namespace:
+ - `embeddings.create(...)`
+ - `embeddings.create_async(...)`
+ - `embeddings.from_request_response(...)`
+
## Integration styles
- Strict wrappers: call OpenAI and record in one step.
@@ -70,6 +75,21 @@ summary = chat.completions.stream(
)
```
+## Embeddings example
+
+```python
+from sigil_sdk_openai import embeddings
+
+embedding_response = embeddings.create(
+ sigil,
+ {
+ "model": "text-embedding-3-small",
+ "input": ["hello", "world"],
+ },
+ lambda request: provider.embeddings.create(**request),
+)
+```
+
## Manual instrumentation example (strict mapper)
```python
diff --git a/python-providers/openai/pyproject.toml b/python-providers/openai/pyproject.toml
index ec6756f..7009cc6 100644
--- a/python-providers/openai/pyproject.toml
+++ b/python-providers/openai/pyproject.toml
@@ -1,12 +1,12 @@
[project]
name = "sigil-sdk-openai"
-version = "0.1.0"
+version = "0.1.2"
description = "OpenAI helper wrappers for Sigil Python SDK"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.10"
dependencies = [
- "sigil-sdk>=0.1.0",
+ "sigil-sdk>=0.1.2",
"openai>=2.20.0,<3",
]
diff --git a/python-providers/openai/sigil_sdk_openai/provider.py b/python-providers/openai/sigil_sdk_openai/provider.py
index 16e3808..3f407bf 100644
--- a/python-providers/openai/sigil_sdk_openai/provider.py
+++ b/python-providers/openai/sigil_sdk_openai/provider.py
@@ -22,6 +22,7 @@
TokenUsage,
ToolCall,
ToolDefinition,
+ ToolResult,
)
if TYPE_CHECKING:
@@ -662,9 +663,20 @@ def _map_chat_request_messages(request: ChatCreateRequest | ChatStreamRequest) -
mapped_role = MessageRole.TOOL
parts: list[Part] = []
- if content:
+ if mapped_role != MessageRole.TOOL and content:
parts.append(Part(kind=PartKind.TEXT, text=content))
+ if mapped_role == MessageRole.TOOL:
+ tool_message = _tool_result_message(
+ _read(message, "content"),
+ tool_call_id=_as_str(_read(message, "tool_call_id")) or _as_str(_read(message, "toolCallId")) or _as_str(_read(message, "id")),
+ name=_as_str(_read(message, "name")),
+ is_error=_read(message, "is_error"),
+ )
+ if tool_message is not None:
+ out.append(tool_message)
+ continue
+
if mapped_role == MessageRole.ASSISTANT:
for part in _map_chat_tool_call_parts(_read(message, "tool_calls")):
parts.append(part)
@@ -831,11 +843,14 @@ def _map_responses_request(request: ResponsesCreateRequest | ResponsesStreamRequ
continue
if item_type == "function_call_output":
- output_text = _extract_text(_read(item, "output")) or _json_text(_read(item, "output"))
- if output_text:
- input_messages.append(
- Message(role=MessageRole.TOOL, parts=[Part(kind=PartKind.TEXT, text=output_text)])
- )
+ tool_message = _tool_result_message(
+ _read(item, "output"),
+ tool_call_id=_as_str(_read(item, "call_id")) or _as_str(_read(item, "callId")),
+ name=_as_str(_read(item, "name")),
+ is_error=_read(item, "is_error"),
+ )
+ if tool_message is not None:
+ input_messages.append(tool_message)
continue
if item_type == "message" or role:
@@ -925,9 +940,14 @@ def _map_responses_output_items(value: Any) -> list[Message]:
continue
if item_type == "function_call_output":
- output_text = _extract_text(_read(item, "output")) or _json_text(_read(item, "output"))
- if output_text:
- out.append(Message(role=MessageRole.TOOL, parts=[Part(kind=PartKind.TEXT, text=output_text)]))
+ tool_message = _tool_result_message(
+ _read(item, "output"),
+ tool_call_id=_as_str(_read(item, "call_id")) or _as_str(_read(item, "callId")),
+ name=_as_str(_read(item, "name")),
+ is_error=_read(item, "is_error"),
+ )
+ if tool_message is not None:
+ out.append(tool_message)
continue
fallback = _extract_text(item)
@@ -937,6 +957,27 @@ def _map_responses_output_items(value: Any) -> list[Message]:
return out
+def _tool_result_message(value: Any, *, tool_call_id: str, name: str, is_error: Any) -> Message | None:
+ content = _extract_text(value)
+ content_json = _json_bytes(value)
+ rendered_content = content or content_json.decode("utf-8")
+ if not rendered_content:
+ return None
+
+ part = Part(
+ kind=PartKind.TOOL_RESULT,
+ tool_result=ToolResult(
+ tool_call_id=tool_call_id,
+ name=name,
+ content=rendered_content,
+ content_json=content_json,
+ is_error=is_error if isinstance(is_error, bool) else None,
+ ),
+ )
+ part.metadata.provider_type = "tool_result"
+ return Message(role=MessageRole.TOOL, parts=[part])
+
+
def _map_responses_usage(value: Any) -> TokenUsage:
usage = TokenUsage(
input_tokens=_as_int(_read(value, "input_tokens")),
diff --git a/python-providers/openai/tests/test_openai_provider.py b/python-providers/openai/tests/test_openai_provider.py
index e6c50e1..0ee6d9b 100644
--- a/python-providers/openai/tests/test_openai_provider.py
+++ b/python-providers/openai/tests/test_openai_provider.py
@@ -279,6 +279,47 @@ def test_openai_wrappers_propagate_provider_error_and_set_call_error() -> None:
client.shutdown()
+def test_openai_wrappers_tolerate_missing_provider_payload_fields() -> None:
+ exporter = _CapturingExporter()
+ client = _new_client(exporter)
+
+ try:
+ chat.completions.create(
+ client,
+ {"model": "gpt-5", "messages": [{"role": "user", "content": "hello"}]},
+ lambda _request: {
+ "id": "resp-chat-malformed",
+ "model": "gpt-5",
+ "object": "chat.completion",
+ "created": 0,
+ "choices": [],
+ },
+ )
+ responses.stream(
+ client,
+ {"model": "gpt-5", "stream": True, "input": "stream this"},
+ lambda _request: ResponsesStreamSummary(events=[{"type": "response.output_text.delta", "delta": 42}]),
+ )
+
+ client.flush()
+ generations = exporter.requests[0].generations
+ assert len(generations) == 2
+
+ chat_generation = generations[0]
+ assert chat_generation.mode.value == "SYNC"
+ assert chat_generation.response_id == "resp-chat-malformed"
+ assert chat_generation.response_model == "gpt-5"
+ assert chat_generation.output == []
+ assert chat_generation.stop_reason == ""
+
+ stream_generation = generations[1]
+ assert stream_generation.mode.value == "STREAM"
+ assert stream_generation.response_model == "gpt-5"
+ assert stream_generation.output[0].parts[0].text == "42"
+ finally:
+ client.shutdown()
+
+
def test_embeddings_wrapper_records_span_and_skips_generation_export() -> None:
exporter = _CapturingExporter()
span_exporter = InMemorySpanExporter()
@@ -391,7 +432,7 @@ def test_chat_mapper_filters_system_messages_and_supports_raw_artifacts() -> Non
{"role": "system", "content": "system"},
{"role": "developer", "content": "developer"},
{"role": "user", "content": "hello"},
- {"role": "tool", "content": '{"ok":true}', "name": "tool-weather"},
+ {"role": "tool", "tool_call_id": "call_weather", "content": '{"ok":true}', "name": "tool-weather"},
],
"tools": [
{
@@ -433,6 +474,10 @@ def test_chat_mapper_filters_system_messages_and_supports_raw_artifacts() -> Non
assert len(mapped_default.input) == 2
assert mapped_default.input[0].role.value == "user"
assert mapped_default.input[1].role.value == "tool"
+ assert mapped_default.input[1].parts[0].kind.value == "tool_result"
+ assert mapped_default.input[1].parts[0].tool_result.tool_call_id == "call_weather"
+ assert mapped_default.input[1].parts[0].tool_result.name == "tool-weather"
+ assert mapped_default.input[1].parts[0].tool_result.content == '{"ok":true}'
assert mapped_default.max_tokens == 320
assert mapped_default.temperature == 0.2
assert mapped_default.top_p == 0.85
@@ -457,7 +502,13 @@ def test_responses_mapper_maps_output_and_stream_fallback() -> None:
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": "hello"}],
- }
+ },
+ {
+ "type": "function_call_output",
+ "call_id": "call_weather",
+ "name": "weather",
+ "output": {"temp_c": 18},
+ },
],
"max_output_tokens": 300,
"reasoning": {"effort": "medium", "max_output_tokens": 640},
@@ -482,6 +533,13 @@ def test_responses_mapper_maps_output_and_stream_fallback() -> None:
"name": "weather",
"arguments": '{"city":"Paris"}',
},
+ {
+ "id": "result-1",
+ "type": "function_call_output",
+ "call_id": "call_weather",
+ "name": "weather",
+ "output": {"temp_c": 18},
+ },
],
"parallel_tool_calls": False,
"temperature": 1,
@@ -502,12 +560,21 @@ def test_responses_mapper_maps_output_and_stream_fallback() -> None:
mapped = responses.from_request_response(request, response)
assert mapped.response_model == "gpt-5"
+ assert len(mapped.input) == 2
+ assert mapped.input[1].role.value == "tool"
+ assert mapped.input[1].parts[0].kind.value == "tool_result"
+ assert mapped.input[1].parts[0].tool_result.tool_call_id == "call_weather"
+ assert mapped.input[1].parts[0].tool_result.content_json == b'{"temp_c":18}'
assert mapped.max_tokens == 300
assert mapped.stop_reason == "stop"
assert mapped.thinking_enabled is True
assert mapped.metadata["sigil.gen_ai.request.thinking.budget_tokens"] == 640
assert mapped.usage.total_tokens == 100
- assert mapped.output
+ assert len(mapped.output) == 3
+ assert mapped.output[2].role.value == "tool"
+ assert mapped.output[2].parts[0].kind.value == "tool_result"
+ assert mapped.output[2].parts[0].tool_result.tool_call_id == "call_weather"
+ assert mapped.output[2].parts[0].tool_result.content_json == b'{"temp_c":18}'
streamed = responses.from_stream(
{**request, "stream": True},
diff --git a/python/LICENSE b/python/LICENSE
index ae8c60c..626a3ab 100644
--- a/python/LICENSE
+++ b/python/LICENSE
@@ -1,3 +1,3 @@
SPDX-License-Identifier: Apache-2.0
-See /sdks/LICENSE at repository root for full license text.
+See /LICENSE at repository root for full license text.
diff --git a/python/README.md b/python/README.md
index 0faf8a9..3caa9ae 100644
--- a/python/README.md
+++ b/python/README.md
@@ -14,6 +14,20 @@ Use this package when you want:
pip install sigil-sdk
```
+## Validation
+
+Run the shared core conformance suite for the Python SDK from the repo root:
+
+```bash
+mise run test:py:sdk-conformance
+```
+
+Run the cross-language aggregate core conformance suite from the repo root:
+
+```bash
+mise run sdk:conformance
+```
+
Optional provider helper packages:
```bash
@@ -284,6 +298,7 @@ Auth is resolved for `generation_export`.
- `mode="none"`
- `mode="tenant"` (requires `tenant_id`, injects `X-Scope-OrgID`)
- `mode="bearer"` (requires `bearer_token`, injects `Authorization: Bearer `)
+- `mode="basic"` (requires `basic_password` + `basic_user` or `tenant_id`, injects `Authorization: Basic `; also injects `X-Scope-OrgID` when `tenant_id` is set — for self-hosted multi-tenancy only, not needed for Grafana Cloud)
Invalid mode/field combinations fail fast in `resolve_config(...)`.
@@ -302,6 +317,38 @@ cfg = ClientConfig(
)
```
+### Grafana Cloud auth (basic)
+
+For Grafana Cloud, use `basic` auth mode. The username is your Grafana Cloud instance/tenant ID and the password is your Grafana Cloud API key:
+
+```python
+import os
+from sigil_sdk import AuthConfig, ClientConfig, GenerationExportConfig
+
+cfg = ClientConfig(
+ generation_export=GenerationExportConfig(
+ protocol="http",
+ endpoint="https://.grafana.net/api/v1/generations:export",
+ auth=AuthConfig(
+ mode="basic",
+ tenant_id=os.environ["GRAFANA_CLOUD_INSTANCE_ID"],
+ basic_password=os.environ["GRAFANA_CLOUD_API_KEY"],
+ ),
+ ),
+)
+```
+
+If your deployment requires a distinct username, set `basic_user` explicitly:
+
+```python
+auth=AuthConfig(
+ mode="basic",
+ tenant_id=os.environ["GRAFANA_CLOUD_INSTANCE_ID"],
+ basic_user=os.environ["GRAFANA_CLOUD_INSTANCE_ID"],
+ basic_password=os.environ["GRAFANA_CLOUD_API_KEY"],
+)
+```
+
## Env-secret wiring example
The SDK does not auto-load env vars. Resolve env values in your application and pass them into config explicitly.
@@ -319,7 +366,8 @@ if gen_token:
Common topology:
-- Generations direct to Sigil: generation `tenant` mode.
+- Grafana Cloud: generation `basic` mode with instance ID and API key.
+- Self-hosted direct to Sigil: generation `tenant` mode.
- Traces/metrics via OTEL Collector/Alloy: configure exporters in your app OTEL SDK setup.
- Enterprise proxy: generation `bearer` mode to proxy; proxy authenticates and forwards tenant header upstream.
diff --git a/python/pyproject.toml b/python/pyproject.toml
index efb4b30..fc5144f 100644
--- a/python/pyproject.toml
+++ b/python/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "sigil-sdk"
-version = "0.1.0"
+version = "0.1.2"
description = "Grafana Sigil Python SDK"
readme = "README.md"
license = { file = "LICENSE" }
diff --git a/python/scripts/generate_proto.sh b/python/scripts/generate_proto.sh
index 7bb246d..ecabe0f 100755
--- a/python/scripts/generate_proto.sh
+++ b/python/scripts/generate_proto.sh
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
-ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
-SDK_DIR="${ROOT_DIR}/sdks/python"
+ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
+SDK_DIR="${ROOT_DIR}/python"
OUT_DIR="${SDK_DIR}/sigil_sdk/internal/gen"
PYTHON_BIN="${PYTHON_BIN:-python3}"
@@ -20,11 +20,11 @@ fi
PROTO_INCLUDE="$(${PYTHON_BIN} -c 'import pathlib, grpc_tools; print(pathlib.Path(grpc_tools.__file__).parent / "_proto")')"
"${PYTHON_BIN}" -m grpc_tools.protoc \
- -I"${ROOT_DIR}/sigil/proto" \
+ -I"${ROOT_DIR}/proto" \
-I"${PROTO_INCLUDE}" \
--python_out="${OUT_DIR}" \
--grpc_python_out="${OUT_DIR}" \
- "${ROOT_DIR}/sigil/proto/sigil/v1/generation_ingest.proto"
+ "${ROOT_DIR}/proto/sigil/v1/generation_ingest.proto"
# The grpc plugin emits absolute import paths; normalize to relative package import.
TMP_FILE="$(mktemp)"
diff --git a/python/setup.py b/python/setup.py
new file mode 100644
index 0000000..b024da8
--- /dev/null
+++ b/python/setup.py
@@ -0,0 +1,4 @@
+from setuptools import setup
+
+
+setup()
diff --git a/python/sigil_sdk/__init__.py b/python/sigil_sdk/__init__.py
index 4468e0f..b50baa1 100644
--- a/python/sigil_sdk/__init__.py
+++ b/python/sigil_sdk/__init__.py
@@ -3,12 +3,16 @@
from .client import Client
from .config import ApiConfig, AuthConfig, ClientConfig, EmbeddingCaptureConfig, GenerationExportConfig, default_config
from .context import (
+ conversation_title_from_context,
conversation_id_from_context,
agent_name_from_context,
agent_version_from_context,
+ user_id_from_context,
with_agent_name,
with_agent_version,
with_conversation_id,
+ with_conversation_title,
+ with_user_id,
)
from .errors import (
ClientShutdownError,
@@ -98,15 +102,19 @@
"agent_version_from_context",
"assistant_text_message",
"conversation_id_from_context",
+ "conversation_title_from_context",
"text_part",
"thinking_part",
"tool_call_part",
"tool_result_message",
"tool_result_part",
+ "user_id_from_context",
"user_text_message",
"with_agent_name",
"with_agent_version",
"with_conversation_id",
+ "with_conversation_title",
+ "with_user_id",
"default_config",
"validate_embedding_result",
"validate_embedding_start",
diff --git a/python/sigil_sdk/client.py b/python/sigil_sdk/client.py
index 8e0e8f0..b17aab0 100644
--- a/python/sigil_sdk/client.py
+++ b/python/sigil_sdk/client.py
@@ -19,7 +19,13 @@
from opentelemetry.trace import Span, SpanKind, Status, StatusCode
from .config import ClientConfig, resolve_config
-from .context import agent_name_from_context, agent_version_from_context, conversation_id_from_context
+from .context import (
+ agent_name_from_context,
+ agent_version_from_context,
+ conversation_id_from_context,
+ conversation_title_from_context,
+ user_id_from_context,
+)
from .errors import (
ClientShutdownError,
EnqueueError,
@@ -61,6 +67,8 @@
_span_attr_framework_langgraph_node = "sigil.framework.langgraph.node"
_span_attr_framework_event_id = "sigil.framework.event_id"
_span_attr_conversation_id = "gen_ai.conversation.id"
+_span_attr_conversation_title = "sigil.conversation.title"
+_span_attr_user_id = "user.id"
_span_attr_agent_name = "gen_ai.agent.name"
_span_attr_agent_version = "gen_ai.agent.version"
_span_attr_error_type = "error.type"
@@ -117,6 +125,8 @@
_instrumentation_name = "github.com/grafana/sigil/sdks/python"
_sdk_name = "sdk-python"
_default_embedding_operation_name = "embeddings"
+_metadata_user_id_key = "sigil.user.id"
+_metadata_legacy_user_id_key = "user.id"
class Client:
@@ -226,9 +236,13 @@ def start_tool_execution(self, start: ToolExecutionStart) -> "ToolExecutionRecor
if seed.tool_name == "":
return NoopToolExecutionRecorder()
+ seed.conversation_title = seed.conversation_title.strip()
if seed.conversation_id == "":
conversation_id = conversation_id_from_context() or ""
seed.conversation_id = conversation_id
+ if seed.conversation_title == "":
+ conversation_title = conversation_title_from_context() or ""
+ seed.conversation_title = conversation_title.strip()
if seed.agent_name == "":
agent_name = agent_name_from_context() or ""
seed.agent_name = agent_name
@@ -379,8 +393,14 @@ def _start_generation(self, start: GenerationStart, default_mode: GenerationMode
if seed.operation_name == "":
seed.operation_name = _default_operation_name(seed.mode)
+ seed.conversation_title = seed.conversation_title.strip()
+ seed.user_id = seed.user_id.strip()
if seed.conversation_id == "":
seed.conversation_id = conversation_id_from_context() or ""
+ if seed.conversation_title == "":
+ seed.conversation_title = (conversation_title_from_context() or "").strip()
+ if seed.user_id == "":
+ seed.user_id = (user_id_from_context() or "").strip()
if seed.agent_name == "":
seed.agent_name = agent_name_from_context() or ""
if seed.agent_version == "":
@@ -399,6 +419,8 @@ def _start_generation(self, start: GenerationStart, default_mode: GenerationMode
Generation(
id=seed.id,
conversation_id=seed.conversation_id,
+ conversation_title=seed.conversation_title,
+ user_id=seed.user_id,
agent_name=seed.agent_name,
agent_version=seed.agent_version,
mode=seed.mode,
@@ -631,8 +653,9 @@ def _record_tool_execution_metrics(
duration_seconds,
attributes={
_span_attr_operation_name: "execute_tool",
- _span_attr_provider_name: "",
- _span_attr_request_model: seed.tool_name,
+ _span_attr_provider_name: seed.request_provider.strip() if seed.request_provider else "",
+ _span_attr_request_model: seed.request_model.strip() if seed.request_model else "",
+ _span_attr_tool_name: seed.tool_name.strip(),
_span_attr_agent_name: seed.agent_name,
_span_attr_error_type: error_type,
_span_attr_error_category: error_category,
@@ -792,6 +815,10 @@ def _normalize_generation(self, raw: Generation, completed_at: datetime, call_er
if generation.conversation_id == "":
generation.conversation_id = self.seed.conversation_id
+ if generation.conversation_title == "":
+ generation.conversation_title = self.seed.conversation_title
+ if generation.user_id == "":
+ generation.user_id = self.seed.user_id
if generation.agent_name == "":
generation.agent_name = self.seed.agent_name
if generation.agent_version == "":
@@ -837,6 +864,22 @@ def _normalize_generation(self, raw: Generation, completed_at: datetime, call_er
merged_metadata.update(generation.metadata)
generation.metadata = merged_metadata
+ conversation_title = generation.conversation_title.strip()
+ if conversation_title == "":
+ conversation_title = _metadata_string_value(generation.metadata, _span_attr_conversation_title) or ""
+ generation.conversation_title = conversation_title
+ if conversation_title != "":
+ generation.metadata[_span_attr_conversation_title] = conversation_title
+
+ user_id = generation.user_id.strip()
+ if user_id == "":
+ user_id = _metadata_string_value(generation.metadata, _metadata_user_id_key) or ""
+ if user_id == "":
+ user_id = _metadata_string_value(generation.metadata, _metadata_legacy_user_id_key) or ""
+ generation.user_id = user_id
+ if user_id != "":
+ generation.metadata[_metadata_user_id_key] = user_id
+
generation.started_at = _to_utc(generation.started_at) if generation.started_at is not None else self.started_at
generation.completed_at = _to_utc(generation.completed_at) if generation.completed_at is not None else completed_at
@@ -1128,6 +1171,10 @@ def _set_generation_span_attributes(span: Span, generation: Generation) -> None:
span.set_attribute(_span_attr_generation_id, generation.id)
if generation.conversation_id:
span.set_attribute(_span_attr_conversation_id, generation.conversation_id)
+ if generation.conversation_title:
+ span.set_attribute(_span_attr_conversation_title, generation.conversation_title)
+ if generation.user_id:
+ span.set_attribute(_span_attr_user_id, generation.user_id)
if generation.agent_name:
span.set_attribute(_span_attr_agent_name, generation.agent_name)
if generation.agent_version:
@@ -1252,10 +1299,16 @@ def _set_tool_span_attributes(span: Span, start: ToolExecutionStart) -> None:
span.set_attribute(_span_attr_tool_description, start.tool_description)
if start.conversation_id:
span.set_attribute(_span_attr_conversation_id, start.conversation_id)
+ if start.conversation_title:
+ span.set_attribute(_span_attr_conversation_title, start.conversation_title)
if start.agent_name:
span.set_attribute(_span_attr_agent_name, start.agent_name)
if start.agent_version:
span.set_attribute(_span_attr_agent_version, start.agent_version)
+ if start.request_provider:
+ span.set_attribute(_span_attr_provider_name, start.request_provider)
+ if start.request_model:
+ span.set_attribute(_span_attr_request_model, start.request_model)
def _thinking_budget_from_metadata(metadata: dict[str, Any]) -> int | None:
diff --git a/python/sigil_sdk/config.py b/python/sigil_sdk/config.py
index fc1dd66..d60395a 100644
--- a/python/sigil_sdk/config.py
+++ b/python/sigil_sdk/config.py
@@ -2,6 +2,7 @@
from __future__ import annotations
+import base64
from dataclasses import dataclass, field
from datetime import datetime, timedelta
import logging
@@ -25,6 +26,8 @@ class AuthConfig:
mode: str = "none"
tenant_id: str = ""
bearer_token: str = ""
+ basic_user: str = ""
+ basic_password: str = ""
@dataclass(slots=True)
@@ -139,8 +142,10 @@ def _resolve_export_headers(headers: dict[str, str], auth: AuthConfig, label: st
out = dict(headers)
if mode == "none":
- if tenant_id or bearer_token:
- raise ValueError(f"{label} auth mode 'none' does not allow tenant_id or bearer_token")
+ basic_user = auth.basic_user.strip()
+ basic_password = auth.basic_password.strip()
+ if tenant_id or bearer_token or basic_user or basic_password:
+ raise ValueError(f"{label} auth mode 'none' does not allow credentials")
return out
if mode == "tenant":
if not tenant_id:
@@ -158,6 +163,21 @@ def _resolve_export_headers(headers: dict[str, str], auth: AuthConfig, label: st
if not _has_header(out, AUTHORIZATION_HEADER):
out[AUTHORIZATION_HEADER] = _format_bearer_token(bearer_token)
return out
+ if mode == "basic":
+ password = auth.basic_password.strip()
+ if not password:
+ raise ValueError(f"{label} auth mode 'basic' requires basic_password")
+ user = auth.basic_user.strip()
+ if not user:
+ user = tenant_id
+ if not user:
+ raise ValueError(f"{label} auth mode 'basic' requires basic_user or tenant_id")
+ if not _has_header(out, AUTHORIZATION_HEADER):
+ creds = base64.b64encode(f"{user}:{password}".encode()).decode()
+ out[AUTHORIZATION_HEADER] = f"Basic {creds}"
+ if tenant_id and not _has_header(out, TENANT_HEADER):
+ out[TENANT_HEADER] = tenant_id
+ return out
raise ValueError(f"unsupported {label} auth mode {auth.mode!r}")
diff --git a/python/sigil_sdk/context.py b/python/sigil_sdk/context.py
index f4a03d5..285a9ee 100644
--- a/python/sigil_sdk/context.py
+++ b/python/sigil_sdk/context.py
@@ -8,6 +8,8 @@
_conversation_id: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar("sigil_conversation_id", default=None)
+_conversation_title: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar("sigil_conversation_title", default=None)
+_user_id: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar("sigil_user_id", default=None)
_agent_name: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar("sigil_agent_name", default=None)
_agent_version: contextvars.ContextVar[Optional[str]] = contextvars.ContextVar("sigil_agent_version", default=None)
@@ -23,6 +25,28 @@ def with_conversation_id(conversation_id: str) -> Iterator[None]:
_conversation_id.reset(token)
+@contextmanager
+def with_conversation_title(conversation_title: str) -> Iterator[None]:
+ """Sets conversation title within a context block."""
+
+ token = _conversation_title.set(conversation_title)
+ try:
+ yield
+ finally:
+ _conversation_title.reset(token)
+
+
+@contextmanager
+def with_user_id(user_id: str) -> Iterator[None]:
+ """Sets user id within a context block."""
+
+ token = _user_id.set(user_id)
+ try:
+ yield
+ finally:
+ _user_id.reset(token)
+
+
@contextmanager
def with_agent_name(agent_name: str) -> Iterator[None]:
"""Sets agent name within a context block."""
@@ -61,3 +85,15 @@ def agent_version_from_context() -> Optional[str]:
"""Returns the current agent version from context variables."""
return _agent_version.get()
+
+
+def conversation_title_from_context() -> Optional[str]:
+ """Returns the current conversation title from context variables."""
+
+ return _conversation_title.get()
+
+
+def user_id_from_context() -> Optional[str]:
+ """Returns the current user id from context variables."""
+
+ return _user_id.get()
diff --git a/python/sigil_sdk/exporters/grpc.py b/python/sigil_sdk/exporters/grpc.py
index 6eefbe3..d8cea91 100644
--- a/python/sigil_sdk/exporters/grpc.py
+++ b/python/sigil_sdk/exporters/grpc.py
@@ -17,7 +17,7 @@ class GRPCGenerationExporter:
def __init__(self, endpoint: str, headers: dict[str, str] | None = None, insecure: bool = False) -> None:
host, implicit_insecure = _parse_endpoint(endpoint)
- self._headers = list((headers or {}).items())
+ self._headers = [(k.lower(), v) for k, v in (headers or {}).items()]
self._channel = grpc.insecure_channel(host) if (insecure or implicit_insecure) else grpc.secure_channel(host, grpc.ssl_channel_credentials())
self._stub = sigil_pb2_grpc.GenerationIngestServiceStub(self._channel)
diff --git a/python/sigil_sdk/framework_handler.py b/python/sigil_sdk/framework_handler.py
index 5270b5a..c3d935e 100644
--- a/python/sigil_sdk/framework_handler.py
+++ b/python/sigil_sdk/framework_handler.py
@@ -304,9 +304,11 @@ def _on_llm_end(self, *, response: Any, run_id: UUID) -> None:
return
try:
- usage = _map_usage(_read(_read(response, "llm_output"), "token_usage"))
- response_model = _as_str(_read(_read(response, "llm_output"), "model_name"))
- stop_reason = _as_str(_read(_read(response, "llm_output"), "finish_reason"))
+ llm_output = _read(response, "llm_output")
+ raw_usage = _read(llm_output, "token_usage") or _read(llm_output, "usage")
+ usage = _map_usage(raw_usage)
+ response_model = _as_str(_read(llm_output, "model_name"))
+ stop_reason = _as_str(_read(llm_output, "finish_reason") or _read(llm_output, "stop_reason"))
output_messages: list[Message] = []
if run_state.capture_outputs:
diff --git a/python/sigil_sdk/internal/gen/sigil/v1/generation_ingest_pb2.py b/python/sigil_sdk/internal/gen/sigil/v1/generation_ingest_pb2.py
index 376dd4b..6d6539f 100644
--- a/python/sigil_sdk/internal/gen/sigil/v1/generation_ingest_pb2.py
+++ b/python/sigil_sdk/internal/gen/sigil/v1/generation_ingest_pb2.py
@@ -26,7 +26,7 @@
from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
-DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n sigil/v1/generation_ingest.proto\x12\x08sigil.v1\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"E\n\x18\x45xportGenerationsRequest\x12)\n\x0bgenerations\x18\x01 \x03(\x0b\x32\x14.sigil.v1.Generation\"N\n\x19\x45xportGenerationsResponse\x12\x31\n\x07results\x18\x01 \x03(\x0b\x32 .sigil.v1.ExportGenerationResult\"P\n\x16\x45xportGenerationResult\x12\x15\n\rgeneration_id\x18\x01 \x01(\t\x12\x10\n\x08\x61\x63\x63\x65pted\x18\x02 \x01(\x08\x12\r\n\x05\x65rror\x18\x03 \x01(\t\"*\n\x08ModelRef\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\"%\n\x0cPartMetadata\x12\x15\n\rprovider_type\x18\x01 \x01(\t\"8\n\x08ToolCall\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x12\n\ninput_json\x18\x03 \x01(\x0c\"i\n\nToolResult\x12\x14\n\x0ctool_call_id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x03 \x01(\t\x12\x14\n\x0c\x63ontent_json\x18\x04 \x01(\x0c\x12\x10\n\x08is_error\x18\x05 \x01(\x08\"\xb5\x01\n\x04Part\x12(\n\x08metadata\x18\x01 \x01(\x0b\x32\x16.sigil.v1.PartMetadata\x12\x0e\n\x04text\x18\x02 \x01(\tH\x00\x12\x12\n\x08thinking\x18\x03 \x01(\tH\x00\x12\'\n\ttool_call\x18\x04 \x01(\x0b\x32\x12.sigil.v1.ToolCallH\x00\x12+\n\x0btool_result\x18\x05 \x01(\x0b\x32\x14.sigil.v1.ToolResultH\x00\x42\t\n\x07payload\"[\n\x07Message\x12#\n\x04role\x18\x01 \x01(\x0e\x32\x15.sigil.v1.MessageRole\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x1d\n\x05parts\x18\x03 \x03(\x0b\x32\x0e.sigil.v1.Part\"\\\n\x0eToolDefinition\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12\x0c\n\x04type\x18\x03 \x01(\t\x12\x19\n\x11input_schema_json\x18\x04 \x01(\x0c\"\xac\x01\n\nTokenUsage\x12\x14\n\x0cinput_tokens\x18\x01 \x01(\x03\x12\x15\n\routput_tokens\x18\x02 \x01(\x03\x12\x14\n\x0ctotal_tokens\x18\x03 \x01(\x03\x12\x1f\n\x17\x63\x61\x63he_read_input_tokens\x18\x04 \x01(\x03\x12 \n\x18\x63\x61\x63he_write_input_tokens\x18\x05 \x01(\x03\x12\x18\n\x10reasoning_tokens\x18\x06 \x01(\x03\"\x85\x01\n\x08\x41rtifact\x12$\n\x04kind\x18\x01 \x01(\x0e\x32\x16.sigil.v1.ArtifactKind\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x14\n\x0c\x63ontent_type\x18\x03 \x01(\t\x12\x0f\n\x07payload\x18\x04 \x01(\x0c\x12\x11\n\trecord_id\x18\x05 \x01(\t\x12\x0b\n\x03uri\x18\x06 \x01(\t\"\xc3\x07\n\nGeneration\x12\n\n\x02id\x18\x01 \x01(\t\x12\x17\n\x0f\x63onversation_id\x18\x02 \x01(\t\x12\x16\n\x0eoperation_name\x18\x03 \x01(\t\x12&\n\x04mode\x18\x04 \x01(\x0e\x32\x18.sigil.v1.GenerationMode\x12\x10\n\x08trace_id\x18\x05 \x01(\t\x12\x0f\n\x07span_id\x18\x06 \x01(\t\x12!\n\x05model\x18\x07 \x01(\x0b\x32\x12.sigil.v1.ModelRef\x12\x13\n\x0bresponse_id\x18\x08 \x01(\t\x12\x16\n\x0eresponse_model\x18\t \x01(\t\x12\x15\n\rsystem_prompt\x18\n \x01(\t\x12 \n\x05input\x18\x0b \x03(\x0b\x32\x11.sigil.v1.Message\x12!\n\x06output\x18\x0c \x03(\x0b\x32\x11.sigil.v1.Message\x12\'\n\x05tools\x18\r \x03(\x0b\x32\x18.sigil.v1.ToolDefinition\x12#\n\x05usage\x18\x0e \x01(\x0b\x32\x14.sigil.v1.TokenUsage\x12\x13\n\x0bstop_reason\x18\x0f \x01(\t\x12.\n\nstarted_at\x18\x10 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x30\n\x0c\x63ompleted_at\x18\x11 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12,\n\x04tags\x18\x12 \x03(\x0b\x32\x1e.sigil.v1.Generation.TagsEntry\x12)\n\x08metadata\x18\x13 \x01(\x0b\x32\x17.google.protobuf.Struct\x12)\n\rraw_artifacts\x18\x14 \x03(\x0b\x32\x12.sigil.v1.Artifact\x12\x12\n\ncall_error\x18\x15 \x01(\t\x12\x12\n\nagent_name\x18\x16 \x01(\t\x12\x15\n\ragent_version\x18\x17 \x01(\t\x12\x17\n\nmax_tokens\x18\x18 \x01(\x03H\x00\x88\x01\x01\x12\x18\n\x0btemperature\x18\x19 \x01(\x01H\x01\x88\x01\x01\x12\x12\n\x05top_p\x18\x1a \x01(\x01H\x02\x88\x01\x01\x12\x18\n\x0btool_choice\x18\x1b \x01(\tH\x03\x88\x01\x01\x12\x1d\n\x10thinking_enabled\x18\x1c \x01(\x08H\x04\x88\x01\x01\x1a+\n\tTagsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42\r\n\x0b_max_tokensB\x0e\n\x0c_temperatureB\x08\n\x06_top_pB\x0e\n\x0c_tool_choiceB\x13\n\x11_thinking_enabled*g\n\x0eGenerationMode\x12\x1f\n\x1bGENERATION_MODE_UNSPECIFIED\x10\x00\x12\x18\n\x14GENERATION_MODE_SYNC\x10\x01\x12\x1a\n\x16GENERATION_MODE_STREAM\x10\x02*u\n\x0bMessageRole\x12\x1c\n\x18MESSAGE_ROLE_UNSPECIFIED\x10\x00\x12\x15\n\x11MESSAGE_ROLE_USER\x10\x01\x12\x1a\n\x16MESSAGE_ROLE_ASSISTANT\x10\x02\x12\x15\n\x11MESSAGE_ROLE_TOOL\x10\x03*\x9f\x01\n\x0c\x41rtifactKind\x12\x1d\n\x19\x41RTIFACT_KIND_UNSPECIFIED\x10\x00\x12\x19\n\x15\x41RTIFACT_KIND_REQUEST\x10\x01\x12\x1a\n\x16\x41RTIFACT_KIND_RESPONSE\x10\x02\x12\x17\n\x13\x41RTIFACT_KIND_TOOLS\x10\x03\x12 \n\x1c\x41RTIFACT_KIND_PROVIDER_EVENT\x10\x04\x32w\n\x17GenerationIngestService\x12\\\n\x11\x45xportGenerations\x12\".sigil.v1.ExportGenerationsRequest\x1a#.sigil.v1.ExportGenerationsResponseB>ZZ None:
assert cfg.generation_export.headers["x-scope-orgid"] == "override-tenant"
+def test_resolve_config_basic_auth_with_tenant_id() -> None:
+ cfg = resolve_config(
+ ClientConfig(
+ generation_export=GenerationExportConfig(
+ auth=AuthConfig(mode="basic", tenant_id="42", basic_password="secret"),
+ ),
+ )
+ )
+
+ expected = "Basic " + base64.b64encode(b"42:secret").decode()
+ assert cfg.generation_export.headers["Authorization"] == expected
+ assert cfg.generation_export.headers["X-Scope-OrgID"] == "42"
+
+
+def test_resolve_config_basic_auth_with_explicit_user() -> None:
+ cfg = resolve_config(
+ ClientConfig(
+ generation_export=GenerationExportConfig(
+ auth=AuthConfig(
+ mode="basic",
+ tenant_id="42",
+ basic_user="probe-user",
+ basic_password="secret",
+ ),
+ ),
+ )
+ )
+
+ expected = "Basic " + base64.b64encode(b"probe-user:secret").decode()
+ assert cfg.generation_export.headers["Authorization"] == expected
+ assert cfg.generation_export.headers["X-Scope-OrgID"] == "42"
+
+
+def test_resolve_config_basic_auth_explicit_header_wins() -> None:
+ cfg = resolve_config(
+ ClientConfig(
+ generation_export=GenerationExportConfig(
+ headers={
+ "Authorization": "Basic override",
+ "X-Scope-OrgID": "override-tenant",
+ },
+ auth=AuthConfig(mode="basic", tenant_id="42", basic_password="secret"),
+ ),
+ )
+ )
+
+ assert cfg.generation_export.headers["Authorization"] == "Basic override"
+ assert cfg.generation_export.headers["X-Scope-OrgID"] == "override-tenant"
+
+
@pytest.mark.parametrize(
"auth",
[
@@ -40,9 +92,13 @@ def test_resolve_config_keeps_explicit_headers() -> None:
AuthConfig(mode="bearer"),
AuthConfig(mode="none", tenant_id="tenant-a"),
AuthConfig(mode="none", bearer_token="token"),
+ AuthConfig(mode="none", basic_user="user"),
+ AuthConfig(mode="none", basic_password="secret"),
AuthConfig(mode="tenant", tenant_id="tenant-a", bearer_token="token"),
AuthConfig(mode="bearer", tenant_id="tenant-a", bearer_token="token"),
AuthConfig(mode="unknown", tenant_id="tenant-a"),
+ AuthConfig(mode="basic"),
+ AuthConfig(mode="basic", basic_password="secret"),
],
)
def test_resolve_config_rejects_invalid_auth_combinations(auth: AuthConfig) -> None:
diff --git a/python/tests/test_conformance.py b/python/tests/test_conformance.py
new file mode 100644
index 0000000..bec1b0a
--- /dev/null
+++ b/python/tests/test_conformance.py
@@ -0,0 +1,669 @@
+"""Core conformance suite for the Sigil Python SDK."""
+
+from __future__ import annotations
+
+import concurrent.futures
+import copy
+from contextlib import nullcontext
+import json
+from http.server import BaseHTTPRequestHandler, HTTPServer
+import socket
+import threading
+from datetime import datetime, timedelta, timezone
+from typing import Any
+
+import grpc
+from opentelemetry.sdk.metrics import MeterProvider
+from opentelemetry.sdk.metrics.export import InMemoryMetricReader
+from opentelemetry.sdk.trace import TracerProvider
+from opentelemetry.sdk.trace.export import SimpleSpanProcessor
+from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
+
+from sigil_sdk import (
+ ApiConfig,
+ Client,
+ ClientConfig,
+ ConversationRatingInput,
+ ConversationRatingValue,
+ EmbeddingResult,
+ EmbeddingStart,
+ Generation,
+ GenerationExportConfig,
+ GenerationMode,
+ GenerationStart,
+ Message,
+ MessageRole,
+ ModelRef,
+ Part,
+ PartKind,
+ TokenUsage,
+ ToolCall,
+ ToolDefinition,
+ ToolExecutionEnd,
+ ToolExecutionStart,
+ ToolResult,
+ with_agent_name,
+ with_agent_version,
+ with_conversation_title,
+ with_user_id,
+)
+from sigil_sdk.internal.gen.sigil.v1 import generation_ingest_pb2 as sigil_pb2
+from sigil_sdk.internal.gen.sigil.v1 import generation_ingest_pb2_grpc as sigil_pb2_grpc
+
+
+_metadata_conversation_title = "sigil.conversation.title"
+_metadata_user_id = "sigil.user.id"
+_metadata_legacy_user_id = "user.id"
+_span_attr_conversation_title = "sigil.conversation.title"
+_span_attr_user_id = "user.id"
+
+
+class _CapturingGenerationServicer(sigil_pb2_grpc.GenerationIngestServiceServicer):
+ def __init__(self) -> None:
+ self.requests: list[sigil_pb2.ExportGenerationsRequest] = []
+ self._lock = threading.Lock()
+
+ def ExportGenerations(self, request, _context): # noqa: N802
+ with self._lock:
+ self.requests.append(copy.deepcopy(request))
+ return sigil_pb2.ExportGenerationsResponse(
+ results=[
+ sigil_pb2.ExportGenerationResult(generation_id=generation.id, accepted=True)
+ for generation in request.generations
+ ]
+ )
+
+ def single_generation(self) -> sigil_pb2.Generation:
+ assert len(self.requests) == 1
+ assert len(self.requests[0].generations) == 1
+ return self.requests[0].generations[0]
+
+
+class _RatingCaptureServer:
+ def __init__(self) -> None:
+ self.requests: list[dict[str, Any]] = []
+
+ class _Handler(BaseHTTPRequestHandler):
+ def do_POST(handler): # noqa: N802
+ length = int(handler.headers.get("Content-Length", "0"))
+ body = handler.rfile.read(length)
+ self.requests.append(
+ {
+ "path": handler.path,
+ "headers": {k.lower(): v for k, v in handler.headers.items()},
+ "payload": json.loads(body.decode("utf-8")),
+ }
+ )
+ encoded = json.dumps(
+ {
+ "rating": {
+ "rating_id": "rat-1",
+ "conversation_id": "conv-rating",
+ "rating": "CONVERSATION_RATING_VALUE_BAD",
+ "created_at": "2026-03-12T09:00:00Z",
+ },
+ "summary": {
+ "total_count": 1,
+ "good_count": 0,
+ "bad_count": 1,
+ "latest_rating": "CONVERSATION_RATING_VALUE_BAD",
+ "latest_rated_at": "2026-03-12T09:00:00Z",
+ "has_bad_rating": True,
+ },
+ }
+ ).encode("utf-8")
+ handler.send_response(200)
+ handler.send_header("Content-Type", "application/json")
+ handler.send_header("Content-Length", str(len(encoded)))
+ handler.end_headers()
+ handler.wfile.write(encoded)
+
+ def log_message(self, _format, *_args): # noqa: A003
+ return
+
+ self.server = HTTPServer(("127.0.0.1", 0), _Handler)
+ self.thread = threading.Thread(target=self.server.serve_forever, daemon=True)
+ self.thread.start()
+
+ @property
+ def endpoint(self) -> str:
+ return f"http://127.0.0.1:{self.server.server_address[1]}"
+
+ def close(self) -> None:
+ self.server.shutdown()
+ self.server.server_close()
+ self.thread.join(timeout=2)
+
+
+class _ConformanceEnv:
+ def __init__(self, *, batch_size: int = 1, flush_interval: timedelta | None = None) -> None:
+ self.servicer = _CapturingGenerationServicer()
+ self.grpc_server = grpc.server(concurrent.futures.ThreadPoolExecutor(max_workers=2))
+ sigil_pb2_grpc.add_GenerationIngestServiceServicer_to_server(self.servicer, self.grpc_server)
+
+ sock = socket.socket()
+ sock.bind(("127.0.0.1", 0))
+ port = sock.getsockname()[1]
+ sock.close()
+
+ self.grpc_server.add_insecure_port(f"127.0.0.1:{port}")
+ self.grpc_server.start()
+
+ self.rating_server = _RatingCaptureServer()
+ self.span_exporter = InMemorySpanExporter()
+ self.tracer_provider = TracerProvider()
+ self.tracer_provider.add_span_processor(SimpleSpanProcessor(self.span_exporter))
+ self.metric_reader = InMemoryMetricReader()
+ self.meter_provider = MeterProvider(metric_readers=[self.metric_reader])
+
+ export_config = GenerationExportConfig(
+ protocol="grpc",
+ endpoint=f"127.0.0.1:{port}",
+ insecure=True,
+ batch_size=batch_size,
+ flush_interval=flush_interval or timedelta(hours=1),
+ max_retries=1,
+ initial_backoff=timedelta(milliseconds=1),
+ max_backoff=timedelta(milliseconds=2),
+ )
+ self.client = Client(
+ ClientConfig(
+ tracer=self.tracer_provider.get_tracer("sigil-conformance-test"),
+ meter=self.meter_provider.get_meter("sigil-conformance-test"),
+ generation_export=export_config,
+ api=ApiConfig(endpoint=self.rating_server.endpoint),
+ )
+ )
+ self._closed = False
+
+ def shutdown(self) -> None:
+ if self._closed:
+ return
+ self._closed = True
+ self.client.shutdown()
+ self.tracer_provider.shutdown()
+ self.meter_provider.shutdown()
+ self.grpc_server.stop(grace=0)
+ self.rating_server.close()
+
+ def generation_span(self):
+ spans = [
+ span
+ for span in self.span_exporter.get_finished_spans()
+ if span.attributes.get("gen_ai.operation.name") in {"generateText", "streamText"}
+ ]
+ assert spans
+ return spans[-1]
+
+ def latest_span(self, operation_name: str):
+ spans = [
+ span
+ for span in self.span_exporter.get_finished_spans()
+ if span.attributes.get("gen_ai.operation.name") == operation_name
+ ]
+ assert spans
+ return spans[-1]
+
+ def metrics(self) -> dict[str, Any]:
+ metrics = {}
+ data = self.metric_reader.get_metrics_data()
+ for resource_metric in data.resource_metrics:
+ for scope_metric in resource_metric.scope_metrics:
+ for metric in scope_metric.metrics:
+ metrics[metric.name] = metric.data
+ return metrics
+
+
+def test_conformance_sync_roundtrip_semantics() -> None:
+ env = _ConformanceEnv()
+ try:
+ recorder = env.client.start_generation(
+ GenerationStart(
+ id="gen-roundtrip",
+ conversation_id="conv-roundtrip",
+ conversation_title="Roundtrip conversation",
+ user_id="user-roundtrip",
+ agent_name="agent-roundtrip",
+ agent_version="v-roundtrip",
+ model=ModelRef(provider="openai", name="gpt-5"),
+ max_tokens=256,
+ temperature=0.2,
+ top_p=0.9,
+ tool_choice="required",
+ thinking_enabled=False,
+ tools=[ToolDefinition(name="weather", description="Get weather", type="function")],
+ tags={"tenant": "dev"},
+ metadata={"trace": "roundtrip"},
+ )
+ )
+ recorder.set_result(
+ Generation(
+ response_id="resp-roundtrip",
+ response_model="gpt-5-2026",
+ input=[
+ Message(
+ role=MessageRole.USER,
+ parts=[Part(kind=PartKind.TEXT, text="hello")],
+ )
+ ],
+ output=[
+ Message(
+ role=MessageRole.ASSISTANT,
+ parts=[
+ Part(kind=PartKind.THINKING, thinking="reasoning"),
+ Part(
+ kind=PartKind.TOOL_CALL,
+ tool_call=ToolCall(id="call-1", name="weather", input_json=b'{"city":"Paris"}'),
+ ),
+ ],
+ ),
+ Message(
+ role=MessageRole.TOOL,
+ parts=[
+ Part(
+ kind=PartKind.TOOL_RESULT,
+ tool_result=ToolResult(
+ tool_call_id="call-1",
+ name="weather",
+ content="sunny",
+ content_json=b'{"temp_c":18}',
+ ),
+ )
+ ],
+ ),
+ ],
+ usage=TokenUsage(
+ input_tokens=12,
+ output_tokens=7,
+ total_tokens=19,
+ cache_read_input_tokens=2,
+ cache_write_input_tokens=1,
+ cache_creation_input_tokens=3,
+ reasoning_tokens=4,
+ ),
+ stop_reason="stop",
+ tags={"region": "eu"},
+ metadata={"result": "ok"},
+ )
+ )
+ recorder.end()
+ env.shutdown()
+
+ generation = env.servicer.single_generation()
+ span = env.generation_span()
+ metrics = env.metrics()
+
+ assert generation.mode == sigil_pb2.GENERATION_MODE_SYNC
+ assert generation.operation_name == "generateText"
+ assert generation.conversation_id == "conv-roundtrip"
+ assert generation.agent_name == "agent-roundtrip"
+ assert generation.agent_version == "v-roundtrip"
+ assert generation.trace_id == span.context.trace_id.to_bytes(16, "big").hex()
+ assert generation.span_id == span.context.span_id.to_bytes(8, "big").hex()
+ assert generation.metadata.fields[_metadata_conversation_title].string_value == "Roundtrip conversation"
+ assert generation.metadata.fields[_metadata_user_id].string_value == "user-roundtrip"
+ assert generation.input[0].parts[0].text == "hello"
+ assert generation.output[0].parts[0].thinking == "reasoning"
+ assert generation.output[0].parts[1].tool_call.name == "weather"
+ assert generation.output[1].parts[0].tool_result.content == "sunny"
+ assert generation.max_tokens == 256
+ assert generation.temperature == 0.2
+ assert generation.top_p == 0.9
+ assert generation.tool_choice == "required"
+ assert generation.thinking_enabled is False
+ assert generation.usage.input_tokens == 12
+ assert generation.usage.output_tokens == 7
+ assert generation.usage.total_tokens == 19
+ assert generation.usage.cache_read_input_tokens == 2
+ assert generation.usage.cache_write_input_tokens == 1
+ assert generation.usage.reasoning_tokens == 4
+ assert generation.stop_reason == "stop"
+ assert generation.tags["tenant"] == "dev"
+ assert generation.tags["region"] == "eu"
+
+ assert span.attributes["gen_ai.operation.name"] == "generateText"
+ assert span.attributes[_span_attr_conversation_title] == "Roundtrip conversation"
+ assert span.attributes[_span_attr_user_id] == "user-roundtrip"
+ assert "gen_ai.client.operation.duration" in metrics
+ assert "gen_ai.client.token.usage" in metrics
+ assert "gen_ai.client.time_to_first_token" not in metrics
+ finally:
+ env.shutdown()
+
+
+def test_conformance_conversation_title_semantics() -> None:
+ cases = [
+ ("explicit wins", "Explicit", "Context", "Meta", "Explicit"),
+ ("context fallback", "", "Context", "", "Context"),
+ ("metadata fallback", "", "", "Meta", "Meta"),
+ ("whitespace trimmed", " Padded ", "", "", "Padded"),
+ ("whitespace omitted", " ", "", "", ""),
+ ]
+
+ for _, start_title, context_title, metadata_title, want_title in cases:
+ env = _ConformanceEnv()
+ try:
+ context = with_conversation_title(context_title) if context_title else nullcontext()
+ with context:
+ start = GenerationStart(
+ model=ModelRef(provider="openai", name="gpt-5"),
+ conversation_title=start_title,
+ metadata={_metadata_conversation_title: metadata_title} if metadata_title else {},
+ )
+ recorder = env.client.start_generation(start)
+ recorder.set_result(Generation())
+ recorder.end()
+ env.shutdown()
+
+ generation = env.servicer.single_generation()
+ span = env.generation_span()
+ field = generation.metadata.fields.get(_metadata_conversation_title)
+ if want_title == "":
+ assert field is None
+ assert _span_attr_conversation_title not in span.attributes
+ else:
+ assert field is not None
+ assert field.string_value == want_title
+ assert span.attributes[_span_attr_conversation_title] == want_title
+ finally:
+ env.shutdown()
+
+
+def test_conformance_user_id_semantics() -> None:
+ cases = [
+ ("explicit wins", "explicit", "ctx", "canonical", "legacy", "explicit"),
+ ("context fallback", "", "ctx", "", "", "ctx"),
+ ("canonical metadata", "", "", "canonical", "", "canonical"),
+ ("legacy metadata", "", "", "", "legacy", "legacy"),
+ ("canonical beats legacy", "", "", "canonical", "legacy", "canonical"),
+ ("whitespace trimmed", " padded ", "", "", "", "padded"),
+ ]
+
+ for _, start_user_id, context_user_id, canonical_user_id, legacy_user_id, want_user_id in cases:
+ env = _ConformanceEnv()
+ try:
+ metadata = {}
+ if canonical_user_id:
+ metadata[_metadata_user_id] = canonical_user_id
+ if legacy_user_id:
+ metadata[_metadata_legacy_user_id] = legacy_user_id
+
+ context = with_user_id(context_user_id) if context_user_id else nullcontext()
+ with context:
+ recorder = env.client.start_generation(
+ GenerationStart(
+ model=ModelRef(provider="openai", name="gpt-5"),
+ user_id=start_user_id,
+ metadata=metadata,
+ )
+ )
+ recorder.set_result(Generation())
+ recorder.end()
+ env.shutdown()
+
+ generation = env.servicer.single_generation()
+ span = env.generation_span()
+ assert generation.metadata.fields[_metadata_user_id].string_value == want_user_id
+ assert span.attributes[_span_attr_user_id] == want_user_id
+ finally:
+ env.shutdown()
+
+
+def test_conformance_agent_identity_semantics() -> None:
+ cases = [
+ ("explicit fields", "agent-explicit", "v1.2.3", "", "", "", "", "agent-explicit", "v1.2.3"),
+ ("context fallback", "", "", "agent-context", "v-context", "", "", "agent-context", "v-context"),
+ ("result-time override", "agent-seed", "v-seed", "", "", "agent-result", "v-result", "agent-result", "v-result"),
+ ("empty omission", "", "", "", "", "", "", "", ""),
+ ]
+
+ for _, start_name, start_version, context_name, context_version, result_name, result_version, want_name, want_version in cases:
+ env = _ConformanceEnv()
+ try:
+ with with_agent_name(context_name) if context_name else nullcontext():
+ with with_agent_version(context_version) if context_version else nullcontext():
+ recorder = env.client.start_generation(
+ GenerationStart(
+ model=ModelRef(provider="openai", name="gpt-5"),
+ agent_name=start_name,
+ agent_version=start_version,
+ )
+ )
+ recorder.set_result(
+ Generation(
+ agent_name=result_name,
+ agent_version=result_version,
+ )
+ )
+ recorder.end()
+ env.shutdown()
+
+ generation = env.servicer.single_generation()
+ span = env.generation_span()
+ assert generation.agent_name == want_name
+ assert generation.agent_version == want_version
+ if want_name:
+ assert span.attributes["gen_ai.agent.name"] == want_name
+ else:
+ assert "gen_ai.agent.name" not in span.attributes
+ if want_version:
+ assert span.attributes["gen_ai.agent.version"] == want_version
+ else:
+ assert "gen_ai.agent.version" not in span.attributes
+ finally:
+ env.shutdown()
+
+
+def test_conformance_streaming_telemetry_semantics() -> None:
+ env = _ConformanceEnv()
+ try:
+ start = GenerationStart(model=ModelRef(provider="openai", name="gpt-5"))
+ recorder = env.client.start_streaming_generation(start)
+ recorder.set_first_token_at(datetime(2026, 3, 12, 9, 0, 0, 250000, tzinfo=timezone.utc))
+ recorder.set_result(
+ Generation(
+ output=[
+ Message(
+ role=MessageRole.ASSISTANT,
+ parts=[Part(kind=PartKind.TEXT, text="Hello world")],
+ )
+ ],
+ usage=TokenUsage(input_tokens=4, output_tokens=3, total_tokens=7),
+ started_at=datetime(2026, 3, 12, 9, 0, 0, tzinfo=timezone.utc),
+ completed_at=datetime(2026, 3, 12, 9, 0, 1, tzinfo=timezone.utc),
+ )
+ )
+ recorder.end()
+ env.shutdown()
+
+ generation = env.servicer.single_generation()
+ span = env.generation_span()
+ metrics = env.metrics()
+
+ assert generation.mode == sigil_pb2.GENERATION_MODE_STREAM
+ assert generation.operation_name == "streamText"
+ assert generation.output[0].parts[0].text == "Hello world"
+ assert span.name == "streamText gpt-5"
+ assert "gen_ai.client.operation.duration" in metrics
+ assert "gen_ai.client.time_to_first_token" in metrics
+ finally:
+ env.shutdown()
+
+
+def test_conformance_tool_execution_semantics() -> None:
+ env = _ConformanceEnv()
+ try:
+ with with_conversation_title("Context title"):
+ with with_agent_name("agent-context"):
+ with with_agent_version("v-context"):
+ recorder = env.client.start_tool_execution(
+ ToolExecutionStart(
+ tool_name="weather",
+ tool_call_id="call-weather-1",
+ tool_type="function",
+ include_content=True,
+ )
+ )
+ recorder.set_result(
+ ToolExecutionEnd(
+ arguments={"city": "Paris"},
+ result={"forecast": "sunny"},
+ )
+ )
+ recorder.end()
+ env.shutdown()
+
+ span = env.latest_span("execute_tool")
+ metrics = env.metrics()
+
+ assert env.servicer.requests == []
+ assert span.name == "execute_tool weather"
+ assert span.attributes["gen_ai.operation.name"] == "execute_tool"
+ assert span.attributes["gen_ai.tool.name"] == "weather"
+ assert span.attributes["gen_ai.tool.call.id"] == "call-weather-1"
+ assert span.attributes["gen_ai.tool.type"] == "function"
+ assert "Paris" in str(span.attributes["gen_ai.tool.call.arguments"])
+ assert "sunny" in str(span.attributes["gen_ai.tool.call.result"])
+ assert span.attributes[_span_attr_conversation_title] == "Context title"
+ assert span.attributes["gen_ai.agent.name"] == "agent-context"
+ assert span.attributes["gen_ai.agent.version"] == "v-context"
+ assert "gen_ai.client.operation.duration" in metrics
+ assert "gen_ai.client.time_to_first_token" not in metrics
+ finally:
+ env.shutdown()
+
+
+def test_conformance_embedding_semantics() -> None:
+ env = _ConformanceEnv()
+ try:
+ with with_agent_name("agent-context"):
+ with with_agent_version("v-context"):
+ recorder = env.client.start_embedding(
+ EmbeddingStart(
+ model=ModelRef(provider="openai", name="text-embedding-3-small"),
+ dimensions=512,
+ )
+ )
+ recorder.set_result(
+ EmbeddingResult(
+ input_count=2,
+ input_tokens=8,
+ input_texts=["hello", "world"],
+ response_model="text-embedding-3-small",
+ dimensions=512,
+ )
+ )
+ recorder.end()
+ env.shutdown()
+
+ span = env.latest_span("embeddings")
+ metrics = env.metrics()
+
+ assert env.servicer.requests == []
+ assert span.name == "embeddings text-embedding-3-small"
+ assert span.attributes["gen_ai.operation.name"] == "embeddings"
+ assert span.attributes["gen_ai.agent.name"] == "agent-context"
+ assert span.attributes["gen_ai.agent.version"] == "v-context"
+ assert span.attributes["gen_ai.embeddings.input_count"] == 2
+ assert span.attributes["gen_ai.embeddings.dimension.count"] == 512
+ assert span.attributes["gen_ai.response.model"] == "text-embedding-3-small"
+ assert "gen_ai.client.operation.duration" in metrics
+ assert "gen_ai.client.token.usage" in metrics
+ assert "gen_ai.client.time_to_first_token" not in metrics
+ assert "gen_ai.client.tool_calls_per_operation" not in metrics
+ finally:
+ env.shutdown()
+
+
+def test_conformance_validation_and_error_semantics() -> None:
+ env = _ConformanceEnv()
+ try:
+ invalid = env.client.start_generation(
+ GenerationStart(model=ModelRef(provider="anthropic", name="claude-sonnet-4-5"))
+ )
+ invalid.set_result(
+ Generation(
+ input=[
+ Message(
+ role=MessageRole.USER,
+ parts=[Part(kind=PartKind.TOOL_CALL, tool_call=ToolCall(name="weather"))],
+ )
+ ]
+ )
+ )
+ invalid.end()
+
+ assert invalid.err() is not None
+ assert env.servicer.requests == []
+ assert env.generation_span().attributes["error.type"] == "validation_error"
+
+ call_error = env.client.start_generation(
+ GenerationStart(model=ModelRef(provider="openai", name="gpt-5"))
+ )
+ call_error.set_call_error(RuntimeError("provider unavailable"))
+ call_error.set_result(Generation())
+ call_error.end()
+ env.shutdown()
+
+ generation = env.servicer.single_generation()
+ spans = env.span_exporter.get_finished_spans()
+ assert call_error.err() is None
+ assert generation.call_error == "provider unavailable"
+ assert generation.metadata.fields["call_error"].string_value == "provider unavailable"
+ assert spans[-1].attributes["error.type"] == "provider_call_error"
+ finally:
+ env.shutdown()
+
+
+def test_conformance_rating_submission_semantics() -> None:
+ env = _ConformanceEnv()
+ try:
+ response = env.client.submit_conversation_rating(
+ "conv-rating",
+ ConversationRatingInput(
+ rating_id="rat-1",
+ rating=ConversationRatingValue.BAD,
+ comment="wrong answer",
+ metadata={"channel": "assistant"},
+ ),
+ )
+ env.shutdown()
+
+ assert len(env.rating_server.requests) == 1
+ request = env.rating_server.requests[0]
+ assert request["path"] == "/api/v1/conversations/conv-rating/ratings"
+ assert request["payload"] == {
+ "rating_id": "rat-1",
+ "rating": "CONVERSATION_RATING_VALUE_BAD",
+ "comment": "wrong answer",
+ "metadata": {"channel": "assistant"},
+ }
+ assert response.rating.conversation_id == "conv-rating"
+ assert response.summary.bad_count == 1
+ finally:
+ env.shutdown()
+
+
+def test_conformance_shutdown_flush_semantics() -> None:
+ env = _ConformanceEnv(batch_size=10)
+ try:
+ recorder = env.client.start_generation(
+ GenerationStart(
+ conversation_id="conv-shutdown",
+ agent_name="agent-shutdown",
+ agent_version="v-shutdown",
+ model=ModelRef(provider="openai", name="gpt-5"),
+ )
+ )
+ recorder.set_result(Generation())
+ recorder.end()
+
+ assert env.servicer.requests == []
+ env.shutdown()
+
+ generation = env.servicer.single_generation()
+ assert generation.conversation_id == "conv-shutdown"
+ assert generation.agent_name == "agent-shutdown"
+ assert generation.agent_version == "v-shutdown"
+ finally:
+ env.shutdown()
diff --git a/python/tests/test_runtime.py b/python/tests/test_runtime.py
index 8cc5236..ca58a20 100644
--- a/python/tests/test_runtime.py
+++ b/python/tests/test_runtime.py
@@ -573,6 +573,8 @@ def test_tool_execution_attributes_and_content_capture() -> None:
conversation_id="conv-tool",
agent_name="agent-tools",
agent_version="2026.02.12",
+ request_provider="openai",
+ request_model="gpt-5",
include_content=True,
)
) as rec:
@@ -585,6 +587,8 @@ def test_tool_execution_attributes_and_content_capture() -> None:
assert span.attributes.get("gen_ai.tool.call.id") == "call_weather"
assert span.attributes.get("gen_ai.tool.call.arguments") is not None
assert span.attributes.get("gen_ai.tool.call.result") is not None
+ assert span.attributes.get("gen_ai.provider.name") == "openai"
+ assert span.attributes.get("gen_ai.request.model") == "gpt-5"
assert span.attributes.get("sigil.sdk.name") == "sdk-python"
finally:
client.shutdown()
diff --git a/python/tests/test_transport.py b/python/tests/test_transport.py
index d1857dd..5307f7c 100644
--- a/python/tests/test_transport.py
+++ b/python/tests/test_transport.py
@@ -265,6 +265,50 @@ def test_sdk_generation_auth_bearer_over_grpc_with_header_override() -> None:
grpc_server.stop(grace=0)
+def test_grpc_metadata_keys_are_lowercased() -> None:
+ """Mixed-case header keys must be lowercased for gRPC metadata (grpcio rejects uppercase)."""
+ servicer = _CapturingGenerationServicer()
+ grpc_server = grpc.server(thread_pool=__import__("concurrent.futures").futures.ThreadPoolExecutor(max_workers=2))
+ sigil_pb2_grpc.add_GenerationIngestServiceServicer_to_server(servicer, grpc_server)
+
+ sock = socket.socket()
+ sock.bind(("127.0.0.1", 0))
+ port = sock.getsockname()[1]
+ sock.close()
+
+ grpc_server.add_insecure_port(f"127.0.0.1:{port}")
+ grpc_server.start()
+
+ client = _new_client(
+ GenerationExportConfig(
+ protocol="grpc",
+ endpoint=f"127.0.0.1:{port}",
+ insecure=True,
+ auth=AuthConfig(mode="tenant", tenant_id="12345"),
+ batch_size=1,
+ flush_interval=timedelta(seconds=1),
+ max_retries=1,
+ initial_backoff=timedelta(milliseconds=1),
+ max_backoff=timedelta(milliseconds=10),
+ )
+ )
+
+ try:
+ start, result = _payload_fixture()
+ rec = client.start_generation(start)
+ rec.set_result(result)
+ rec.end()
+ assert rec.err() is None
+ client.shutdown()
+
+ assert len(servicer.metadata) == 1
+ meta = servicer.metadata[0]
+ assert meta.get("x-scope-orgid") == "12345"
+ assert not any(k != k.lower() for k in meta)
+ finally:
+ grpc_server.stop(grace=0)
+
+
def _assert_generation_json_payload(generation: dict[str, Any]) -> None:
assert generation["id"] == "gen-fixture-1"
assert generation["conversation_id"] == "conv-fixture-1"
diff --git a/python/uv.lock b/python/uv.lock
new file mode 100644
index 0000000..b5119bd
--- /dev/null
+++ b/python/uv.lock
@@ -0,0 +1,592 @@
+version = 1
+revision = 3
+requires-python = ">=3.10"
+resolution-markers = [
+ "python_full_version >= '3.14'",
+ "python_full_version == '3.13.*'",
+ "python_full_version < '3.13'",
+]
+
+[[package]]
+name = "certifi"
+version = "2026.2.25"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a7/21/a2b1505639008ba2e6ef03733a81fc6cfd6a07ea6139a2b76421230b8dad/charset_normalizer-3.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4167a621a9a1a986c73777dbc15d4b5eac8ac5c10393374109a343d4013ec765", size = 283319, upload-time = "2026-03-06T06:00:26.433Z" },
+ { url = "https://files.pythonhosted.org/packages/70/67/df234c29b68f4e1e095885c9db1cb4b69b8aba49cf94fac041db4aaf1267/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f64c6bf8f32f9133b668c7f7a7cbdbc453412bc95ecdbd157f3b1e377a92990", size = 189974, upload-time = "2026-03-06T06:00:28.222Z" },
+ { url = "https://files.pythonhosted.org/packages/df/7f/fc66af802961c6be42e2c7b69c58f95cbd1f39b0e81b3365d8efe2a02a04/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:568e3c34b58422075a1b49575a6abc616d9751b4d61b23f712e12ebb78fe47b2", size = 207866, upload-time = "2026-03-06T06:00:29.769Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/23/404eb36fac4e95b833c50e305bba9a241086d427bb2167a42eac7c4f7da4/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:036c079aa08a6a592b82487f97c60b439428320ed1b2ea0b3912e99d30c77765", size = 203239, upload-time = "2026-03-06T06:00:31.086Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/2f/8a1d989bfadd120c90114ab33e0d2a0cbde05278c1fc15e83e62d570f50a/charset_normalizer-3.4.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:340810d34ef83af92148e96e3e44cb2d3f910d2bf95e5618a5c467d9f102231d", size = 196529, upload-time = "2026-03-06T06:00:32.608Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/0c/c75f85ff7ca1f051958bb518cd43922d86f576c03947a050fbedfdfb4f15/charset_normalizer-3.4.5-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:cd2d0f0ec9aa977a27731a3209ebbcacebebaf41f902bd453a928bfd281cf7f8", size = 184152, upload-time = "2026-03-06T06:00:33.93Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/20/4ed37f6199af5dde94d4aeaf577f3813a5ec6635834cda1d957013a09c76/charset_normalizer-3.4.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b362bcd27819f9c07cbf23db4e0e8cd4b44c5ecd900c2ff907b2b92274a7412", size = 195226, upload-time = "2026-03-06T06:00:35.469Z" },
+ { url = "https://files.pythonhosted.org/packages/28/31/7ba1102178cba7c34dcc050f43d427172f389729e356038f0726253dd914/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:77be992288f720306ab4108fe5c74797de327f3248368dfc7e1a916d6ed9e5a2", size = 192933, upload-time = "2026-03-06T06:00:36.83Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/23/f86443ab3921e6a60b33b93f4a1161222231f6c69bc24fb18f3bee7b8518/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:8b78d8a609a4b82c273257ee9d631ded7fac0d875bdcdccc109f3ee8328cfcb1", size = 185647, upload-time = "2026-03-06T06:00:38.367Z" },
+ { url = "https://files.pythonhosted.org/packages/82/44/08b8be891760f1f5a6d23ce11d6d50c92981603e6eb740b4f72eea9424e2/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ba20bdf69bd127f66d0174d6f2a93e69045e0b4036dc1ca78e091bcc765830c4", size = 209533, upload-time = "2026-03-06T06:00:41.931Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/5f/df114f23406199f8af711ddccfbf409ffbc5b7cdc18fa19644997ff0c9bb/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:76a9d0de4d0eab387822e7b35d8f89367dd237c72e82ab42b9f7bf5e15ada00f", size = 195901, upload-time = "2026-03-06T06:00:43.978Z" },
+ { url = "https://files.pythonhosted.org/packages/07/83/71ef34a76fe8aa05ff8f840244bda2d61e043c2ef6f30d200450b9f6a1be/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8fff79bf5978c693c9b1a4d71e4a94fddfb5fe744eb062a318e15f4a2f63a550", size = 204950, upload-time = "2026-03-06T06:00:45.202Z" },
+ { url = "https://files.pythonhosted.org/packages/58/40/0253be623995365137d7dc68e45245036207ab2227251e69a3d93ce43183/charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c7e84e0c0005e3bdc1a9211cd4e62c78ba80bc37b2365ef4410cd2007a9047f2", size = 198546, upload-time = "2026-03-06T06:00:46.481Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/5c/5f3cb5b259a130895ef5ae16b38eaf141430fa3f7af50cd06c5d67e4f7b2/charset_normalizer-3.4.5-cp310-cp310-win32.whl", hash = "sha256:58ad8270cfa5d4bef1bc85bd387217e14ff154d6630e976c6f56f9a040757475", size = 132516, upload-time = "2026-03-06T06:00:47.924Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/c3/84fb174e7770f2df2e1a2115090771bfbc2227fb39a765c6d00568d1aab4/charset_normalizer-3.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:02a9d1b01c1e12c27883b0c9349e0bcd9ae92e727ff1a277207e1a262b1cbf05", size = 142906, upload-time = "2026-03-06T06:00:49.389Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/b2/6f852f8b969f2cbd0d4092d2e60139ab1af95af9bb651337cae89ec0f684/charset_normalizer-3.4.5-cp310-cp310-win_arm64.whl", hash = "sha256:039215608ac7b358c4da0191d10fc76868567fbf276d54c14721bdedeb6de064", size = 133258, upload-time = "2026-03-06T06:00:51.051Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/9e/bcec3b22c64ecec47d39bf5167c2613efd41898c019dccd4183f6aa5d6a7/charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:610f72c0ee565dfb8ae1241b666119582fdbfe7c0975c175be719f940e110694", size = 279531, upload-time = "2026-03-06T06:00:52.252Z" },
+ { url = "https://files.pythonhosted.org/packages/58/12/81fd25f7e7078ab5d1eedbb0fac44be4904ae3370a3bf4533c8f2d159acd/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60d68e820af339df4ae8358c7a2e7596badeb61e544438e489035f9fbf3246a5", size = 188006, upload-time = "2026-03-06T06:00:53.8Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/6e/f2d30e8c27c1b0736a6520311982cf5286cfc7f6cac77d7bc1325e3a23f2/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b473fc8dca1c3ad8559985794815f06ca3fc71942c969129070f2c3cdf7281", size = 205085, upload-time = "2026-03-06T06:00:55.311Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/90/d12cefcb53b5931e2cf792a33718d7126efb116a320eaa0742c7059a95e4/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d4eb8ac7469b2a5d64b5b8c04f84d8bf3ad340f4514b98523805cbf46e3b3923", size = 200545, upload-time = "2026-03-06T06:00:56.532Z" },
+ { url = "https://files.pythonhosted.org/packages/03/f4/44d3b830a20e89ff82a3134912d9a1cf6084d64f3b95dcad40f74449a654/charset_normalizer-3.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bcb3227c3d9aaf73eaaab1db7ccd80a8995c509ee9941e2aae060ca6e4e5d81", size = 193863, upload-time = "2026-03-06T06:00:57.823Z" },
+ { url = "https://files.pythonhosted.org/packages/25/4b/f212119c18a6320a9d4a730d1b4057875cdeabf21b3614f76549042ef8a8/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:75ee9c1cce2911581a70a3c0919d8bccf5b1cbc9b0e5171400ec736b4b569497", size = 181827, upload-time = "2026-03-06T06:00:59.323Z" },
+ { url = "https://files.pythonhosted.org/packages/74/00/b26158e48b425a202a92965f8069e8a63d9af1481dfa206825d7f74d2a3c/charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d1401945cb77787dbd3af2446ff2d75912327c4c3a1526ab7955ecf8600687c", size = 191085, upload-time = "2026-03-06T06:01:00.546Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/c2/1c1737bf6fd40335fe53d28fe49afd99ee4143cc57a845e99635ce0b9b6d/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a45e504f5e1be0bd385935a8e1507c442349ca36f511a47057a71c9d1d6ea9e", size = 190688, upload-time = "2026-03-06T06:01:02.479Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/3d/abb5c22dc2ef493cd56522f811246a63c5427c08f3e3e50ab663de27fcf4/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e09f671a54ce70b79a1fc1dc6da3072b7ef7251fadb894ed92d9aa8218465a5f", size = 183077, upload-time = "2026-03-06T06:01:04.231Z" },
+ { url = "https://files.pythonhosted.org/packages/44/33/5298ad4d419a58e25b3508e87f2758d1442ff00c2471f8e0403dab8edad5/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d01de5e768328646e6a3fa9e562706f8f6641708c115c62588aef2b941a4f88e", size = 206706, upload-time = "2026-03-06T06:01:05.773Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/17/51e7895ac0f87c3b91d276a449ef09f5532a7529818f59646d7a55089432/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:131716d6786ad5e3dc542f5cc6f397ba3339dc0fb87f87ac30e550e8987756af", size = 191665, upload-time = "2026-03-06T06:01:07.473Z" },
+ { url = "https://files.pythonhosted.org/packages/90/8f/cce9adf1883e98906dbae380d769b4852bb0fa0004bc7d7a2243418d3ea8/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a374cc0b88aa710e8865dc1bd6edb3743c59f27830f0293ab101e4cf3ce9f85", size = 201950, upload-time = "2026-03-06T06:01:08.973Z" },
+ { url = "https://files.pythonhosted.org/packages/08/ca/bce99cd5c397a52919e2769d126723f27a4c037130374c051c00470bcd38/charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d31f0d1671e1534e395f9eb84a68e0fb670e1edb1fe819a9d7f564ae3bc4e53f", size = 195830, upload-time = "2026-03-06T06:01:10.155Z" },
+ { url = "https://files.pythonhosted.org/packages/87/4f/2e3d023a06911f1281f97b8f036edc9872167036ca6f55cc874a0be6c12c/charset_normalizer-3.4.5-cp311-cp311-win32.whl", hash = "sha256:cace89841c0599d736d3d74a27bc5821288bb47c5441923277afc6059d7fbcb4", size = 132029, upload-time = "2026-03-06T06:01:11.706Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/1f/a853b73d386521fd44b7f67ded6b17b7b2367067d9106a5c4b44f9a34274/charset_normalizer-3.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:f8102ae93c0bc863b1d41ea0f4499c20a83229f52ed870850892df555187154a", size = 142404, upload-time = "2026-03-06T06:01:12.865Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/10/dba36f76b71c38e9d391abe0fd8a5b818790e053c431adecfc98c35cd2a9/charset_normalizer-3.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:ed98364e1c262cf5f9363c3eca8c2df37024f52a8fa1180a3610014f26eac51c", size = 132796, upload-time = "2026-03-06T06:01:14.106Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/d8/a54f7c0b96f1df3563e9190f04daf981e365a9b397eedfdfb5dbef7e5c6c/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54", size = 189356, upload-time = "2026-03-06T06:01:16.511Z" },
+ { url = "https://files.pythonhosted.org/packages/42/69/2bf7f76ce1446759a5787cb87d38f6a61eb47dbbdf035cfebf6347292a65/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467", size = 206369, upload-time = "2026-03-06T06:01:17.853Z" },
+ { url = "https://files.pythonhosted.org/packages/10/9c/949d1a46dab56b959d9a87272482195f1840b515a3380e39986989a893ae/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60", size = 203285, upload-time = "2026-03-06T06:01:19.473Z" },
+ { url = "https://files.pythonhosted.org/packages/67/5c/ae30362a88b4da237d71ea214a8c7eb915db3eec941adda511729ac25fa2/charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d", size = 196274, upload-time = "2026-03-06T06:01:20.728Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/07/c9f2cb0e46cb6d64fdcc4f95953747b843bb2181bda678dc4e699b8f0f9a/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e", size = 184715, upload-time = "2026-03-06T06:01:22.194Z" },
+ { url = "https://files.pythonhosted.org/packages/36/64/6b0ca95c44fddf692cd06d642b28f63009d0ce325fad6e9b2b4d0ef86a52/charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f", size = 193426, upload-time = "2026-03-06T06:01:23.795Z" },
+ { url = "https://files.pythonhosted.org/packages/50/bc/a730690d726403743795ca3f5bb2baf67838c5fea78236098f324b965e40/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc", size = 191780, upload-time = "2026-03-06T06:01:25.053Z" },
+ { url = "https://files.pythonhosted.org/packages/97/4f/6c0bc9af68222b22951552d73df4532b5be6447cee32d58e7e8c74ecbb7b/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95", size = 185805, upload-time = "2026-03-06T06:01:26.294Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/b9/a523fb9b0ee90814b503452b2600e4cbc118cd68714d57041564886e7325/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a", size = 208342, upload-time = "2026-03-06T06:01:27.55Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/61/c59e761dee4464050713e50e27b58266cc8e209e518c0b378c1580c959ba/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac", size = 193661, upload-time = "2026-03-06T06:01:29.051Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/43/729fa30aad69783f755c5ad8649da17ee095311ca42024742701e202dc59/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1", size = 204819, upload-time = "2026-03-06T06:01:30.298Z" },
+ { url = "https://files.pythonhosted.org/packages/87/33/d9b442ce5a91b96fc0840455a9e49a611bbadae6122778d0a6a79683dd31/charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98", size = 198080, upload-time = "2026-03-06T06:01:31.478Z" },
+ { url = "https://files.pythonhosted.org/packages/56/5a/b8b5a23134978ee9885cee2d6995f4c27cc41f9baded0a9685eabc5338f0/charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262", size = 132630, upload-time = "2026-03-06T06:01:33.056Z" },
+ { url = "https://files.pythonhosted.org/packages/70/53/e44a4c07e8904500aec95865dc3f6464dc3586a039ef0df606eb3ac38e35/charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636", size = 142856, upload-time = "2026-03-06T06:01:34.489Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/aa/c5628f7cad591b1cf45790b7a61483c3e36cf41349c98af7813c483fd6e8/charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02", size = 132982, upload-time = "2026-03-06T06:01:35.641Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/48/9f34ec4bb24aa3fdba1890c1bddb97c8a4be1bd84ef5c42ac2352563ad05/charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23", size = 280788, upload-time = "2026-03-06T06:01:37.126Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/09/6003e7ffeb90cc0560da893e3208396a44c210c5ee42efff539639def59b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8", size = 188890, upload-time = "2026-03-06T06:01:38.73Z" },
+ { url = "https://files.pythonhosted.org/packages/42/1e/02706edf19e390680daa694d17e2b8eab4b5f7ac285e2a51168b4b22ee6b/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d", size = 206136, upload-time = "2026-03-06T06:01:40.016Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/87/942c3def1b37baf3cf786bad01249190f3ca3d5e63a84f831e704977de1f/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce", size = 202551, upload-time = "2026-03-06T06:01:41.522Z" },
+ { url = "https://files.pythonhosted.org/packages/94/0a/af49691938dfe175d71b8a929bd7e4ace2809c0c5134e28bc535660d5262/charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819", size = 195572, upload-time = "2026-03-06T06:01:43.208Z" },
+ { url = "https://files.pythonhosted.org/packages/20/ea/dfb1792a8050a8e694cfbde1570ff97ff74e48afd874152d38163d1df9ae/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d", size = 184438, upload-time = "2026-03-06T06:01:44.755Z" },
+ { url = "https://files.pythonhosted.org/packages/72/12/c281e2067466e3ddd0595bfaea58a6946765ace5c72dfa3edc2f5f118026/charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763", size = 193035, upload-time = "2026-03-06T06:01:46.051Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/4f/3792c056e7708e10464bad0438a44708886fb8f92e3c3d29ec5e2d964d42/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9", size = 191340, upload-time = "2026-03-06T06:01:47.547Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/86/80ddba897127b5c7a9bccc481b0cd36c8fefa485d113262f0fe4332f0bf4/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c", size = 185464, upload-time = "2026-03-06T06:01:48.764Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/00/b5eff85ba198faacab83e0e4b6f0648155f072278e3b392a82478f8b988b/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67", size = 208014, upload-time = "2026-03-06T06:01:50.371Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/11/d36f70be01597fd30850dde8a1269ebc8efadd23ba5785808454f2389bde/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3", size = 193297, upload-time = "2026-03-06T06:01:51.933Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/1d/259eb0a53d4910536c7c2abb9cb25f4153548efb42800c6a9456764649c0/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf", size = 204321, upload-time = "2026-03-06T06:01:53.887Z" },
+ { url = "https://files.pythonhosted.org/packages/84/31/faa6c5b9d3688715e1ed1bb9d124c384fe2fc1633a409e503ffe1c6398c1/charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6", size = 197509, upload-time = "2026-03-06T06:01:56.439Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/a5/c7d9dd1503ffc08950b3260f5d39ec2366dd08254f0900ecbcf3a6197c7c/charset_normalizer-3.4.5-cp313-cp313-win32.whl", hash = "sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f", size = 132284, upload-time = "2026-03-06T06:01:57.812Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/0f/57072b253af40c8aa6636e6de7d75985624c1eb392815b2f934199340a89/charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7", size = 142630, upload-time = "2026-03-06T06:01:59.062Z" },
+ { url = "https://files.pythonhosted.org/packages/31/41/1c4b7cc9f13bd9d369ce3bc993e13d374ce25fa38a2663644283ecf422c1/charset_normalizer-3.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36", size = 133254, upload-time = "2026-03-06T06:02:00.281Z" },
+ { url = "https://files.pythonhosted.org/packages/43/be/0f0fd9bb4a7fa4fb5067fb7d9ac693d4e928d306f80a0d02bde43a7c4aee/charset_normalizer-3.4.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873", size = 280232, upload-time = "2026-03-06T06:02:01.508Z" },
+ { url = "https://files.pythonhosted.org/packages/28/02/983b5445e4bef49cd8c9da73a8e029f0825f39b74a06d201bfaa2e55142a/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f", size = 189688, upload-time = "2026-03-06T06:02:02.857Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/88/152745c5166437687028027dc080e2daed6fe11cfa95a22f4602591c42db/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4", size = 206833, upload-time = "2026-03-06T06:02:05.127Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/0f/ebc15c8b02af2f19be9678d6eed115feeeccc45ce1f4b098d986c13e8769/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee", size = 202879, upload-time = "2026-03-06T06:02:06.446Z" },
+ { url = "https://files.pythonhosted.org/packages/38/9c/71336bff6934418dc8d1e8a1644176ac9088068bc571da612767619c97b3/charset_normalizer-3.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66", size = 195764, upload-time = "2026-03-06T06:02:08.763Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/95/ce92fde4f98615661871bc282a856cf9b8a15f686ba0af012984660d480b/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362", size = 183728, upload-time = "2026-03-06T06:02:10.137Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/e7/f5b4588d94e747ce45ae680f0f242bc2d98dbd4eccfab73e6160b6893893/charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7", size = 192937, upload-time = "2026-03-06T06:02:11.663Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/29/9d94ed6b929bf9f48bf6ede6e7474576499f07c4c5e878fb186083622716/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d", size = 192040, upload-time = "2026-03-06T06:02:13.489Z" },
+ { url = "https://files.pythonhosted.org/packages/15/d2/1a093a1cf827957f9445f2fe7298bcc16f8fc5e05c1ed2ad1af0b239035e/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6", size = 184107, upload-time = "2026-03-06T06:02:14.83Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/7d/82068ce16bd36135df7b97f6333c5d808b94e01d4599a682e2337ed5fd14/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39", size = 208310, upload-time = "2026-03-06T06:02:16.165Z" },
+ { url = "https://files.pythonhosted.org/packages/84/4e/4dfb52307bb6af4a5c9e73e482d171b81d36f522b21ccd28a49656baa680/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6", size = 192918, upload-time = "2026-03-06T06:02:18.144Z" },
+ { url = "https://files.pythonhosted.org/packages/08/a4/159ff7da662cf7201502ca89980b8f06acf3e887b278956646a8aeb178ab/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94", size = 204615, upload-time = "2026-03-06T06:02:19.821Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/62/0dd6172203cb6b429ffffc9935001fde42e5250d57f07b0c28c6046deb6b/charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e", size = 197784, upload-time = "2026-03-06T06:02:21.86Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/5e/1aab5cb737039b9c59e63627dc8bbc0d02562a14f831cc450e5f91d84ce1/charset_normalizer-3.4.5-cp314-cp314-win32.whl", hash = "sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2", size = 133009, upload-time = "2026-03-06T06:02:23.289Z" },
+ { url = "https://files.pythonhosted.org/packages/40/65/e7c6c77d7aaa4c0d7974f2e403e17f0ed2cb0fc135f77d686b916bf1eead/charset_normalizer-3.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa", size = 143511, upload-time = "2026-03-06T06:02:26.195Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/91/52b0841c71f152f563b8e072896c14e3d83b195c188b338d3cc2e582d1d4/charset_normalizer-3.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4", size = 133775, upload-time = "2026-03-06T06:02:27.473Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/60/3a621758945513adfd4db86827a5bafcc615f913dbd0b4c2ed64a65731be/charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0", size = 55455, upload-time = "2026-03-06T06:03:17.827Z" },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
+]
+
+[[package]]
+name = "googleapis-common-protos"
+version = "1.73.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "protobuf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/99/96/a0205167fa0154f4a542fd6925bdc63d039d88dab3588b875078107e6f06/googleapis_common_protos-1.73.0.tar.gz", hash = "sha256:778d07cd4fbeff84c6f7c72102f0daf98fa2bfd3fa8bea426edc545588da0b5a", size = 147323, upload-time = "2026-03-06T21:53:09.727Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/69/28/23eea8acd65972bbfe295ce3666b28ac510dfcb115fac089d3edb0feb00a/googleapis_common_protos-1.73.0-py3-none-any.whl", hash = "sha256:dfdaaa2e860f242046be561e6d6cb5c5f1541ae02cfbcb034371aadb2942b4e8", size = 297578, upload-time = "2026-03-06T21:52:33.933Z" },
+]
+
+[[package]]
+name = "grpcio"
+version = "1.78.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5a/a8/690a085b4d1fe066130de97a87de32c45062cf2ecd218df9675add895550/grpcio-1.78.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:7cc47943d524ee0096f973e1081cb8f4f17a4615f2116882a5f1416e4cfe92b5", size = 5946986, upload-time = "2026-02-06T09:54:34.043Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/1b/e5213c5c0ced9d2d92778d30529ad5bb2dcfb6c48c4e2d01b1f302d33d64/grpcio-1.78.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c3f293fdc675ccba4db5a561048cca627b5e7bd1c8a6973ffedabe7d116e22e2", size = 11816533, upload-time = "2026-02-06T09:54:37.04Z" },
+ { url = "https://files.pythonhosted.org/packages/18/37/1ba32dccf0a324cc5ace744c44331e300b000a924bf14840f948c559ede7/grpcio-1.78.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10a9a644b5dd5aec3b82b5b0b90d41c0fa94c85ef42cb42cf78a23291ddb5e7d", size = 6519964, upload-time = "2026-02-06T09:54:40.268Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/f5/c0e178721b818072f2e8b6fde13faaba942406c634009caf065121ce246b/grpcio-1.78.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4c5533d03a6cbd7f56acfc9cfb44ea64f63d29091e40e44010d34178d392d7eb", size = 7198058, upload-time = "2026-02-06T09:54:42.389Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/b2/40d43c91ae9cd667edc960135f9f08e58faa1576dc95af29f66ec912985f/grpcio-1.78.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff870aebe9a93a85283837801d35cd5f8814fe2ad01e606861a7fb47c762a2b7", size = 6727212, upload-time = "2026-02-06T09:54:44.91Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/88/9da42eed498f0efcfcd9156e48ae63c0cde3bea398a16c99fb5198c885b6/grpcio-1.78.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:391e93548644e6b2726f1bb84ed60048d4bcc424ce5e4af0843d28ca0b754fec", size = 7300845, upload-time = "2026-02-06T09:54:47.562Z" },
+ { url = "https://files.pythonhosted.org/packages/23/3f/1c66b7b1b19a8828890e37868411a6e6925df5a9030bfa87ab318f34095d/grpcio-1.78.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:df2c8f3141f7cbd112a6ebbd760290b5849cda01884554f7c67acc14e7b1758a", size = 8284605, upload-time = "2026-02-06T09:54:50.475Z" },
+ { url = "https://files.pythonhosted.org/packages/94/c4/ca1bd87394f7b033e88525384b4d1e269e8424ab441ea2fba1a0c5b50986/grpcio-1.78.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd8cb8026e5f5b50498a3c4f196f57f9db344dad829ffae16b82e4fdbaea2813", size = 7726672, upload-time = "2026-02-06T09:54:53.11Z" },
+ { url = "https://files.pythonhosted.org/packages/41/09/f16e487d4cc65ccaf670f6ebdd1a17566b965c74fc3d93999d3b2821e052/grpcio-1.78.0-cp310-cp310-win32.whl", hash = "sha256:f8dff3d9777e5d2703a962ee5c286c239bf0ba173877cc68dc02c17d042e29de", size = 4076715, upload-time = "2026-02-06T09:54:55.549Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/32/4ce60d94e242725fd3bcc5673c04502c82a8e87b21ea411a63992dc39f8f/grpcio-1.78.0-cp310-cp310-win_amd64.whl", hash = "sha256:94f95cf5d532d0e717eed4fc1810e8e6eded04621342ec54c89a7c2f14b581bf", size = 4799157, upload-time = "2026-02-06T09:54:59.838Z" },
+ { url = "https://files.pythonhosted.org/packages/86/c7/d0b780a29b0837bf4ca9580904dfb275c1fc321ded7897d620af7047ec57/grpcio-1.78.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2777b783f6c13b92bd7b716667452c329eefd646bfb3f2e9dabea2e05dbd34f6", size = 5951525, upload-time = "2026-02-06T09:55:01.989Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/b1/96920bf2ee61df85a9503cb6f733fe711c0ff321a5a697d791b075673281/grpcio-1.78.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:9dca934f24c732750389ce49d638069c3892ad065df86cb465b3fa3012b70c9e", size = 11830418, upload-time = "2026-02-06T09:55:04.462Z" },
+ { url = "https://files.pythonhosted.org/packages/83/0c/7c1528f098aeb75a97de2bae18c530f56959fb7ad6c882db45d9884d6edc/grpcio-1.78.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:459ab414b35f4496138d0ecd735fed26f1318af5e52cb1efbc82a09f0d5aa911", size = 6524477, upload-time = "2026-02-06T09:55:07.111Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/52/e7c1f3688f949058e19a011c4e0dec973da3d0ae5e033909677f967ae1f4/grpcio-1.78.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:082653eecbdf290e6e3e2c276ab2c54b9e7c299e07f4221872380312d8cf395e", size = 7198266, upload-time = "2026-02-06T09:55:10.016Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/61/8ac32517c1e856677282c34f2e7812d6c328fa02b8f4067ab80e77fdc9c9/grpcio-1.78.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85f93781028ec63f383f6bc90db785a016319c561cc11151fbb7b34e0d012303", size = 6730552, upload-time = "2026-02-06T09:55:12.207Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/98/b8ee0158199250220734f620b12e4a345955ac7329cfd908d0bf0fda77f0/grpcio-1.78.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f12857d24d98441af6a1d5c87442d624411db486f7ba12550b07788f74b67b04", size = 7304296, upload-time = "2026-02-06T09:55:15.044Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/0f/7b72762e0d8840b58032a56fdbd02b78fc645b9fa993d71abf04edbc54f4/grpcio-1.78.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5397fff416b79e4b284959642a4e95ac4b0f1ece82c9993658e0e477d40551ec", size = 8288298, upload-time = "2026-02-06T09:55:17.276Z" },
+ { url = "https://files.pythonhosted.org/packages/24/ae/ae4ce56bc5bb5caa3a486d60f5f6083ac3469228faa734362487176c15c5/grpcio-1.78.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fbe6e89c7ffb48518384068321621b2a69cab509f58e40e4399fdd378fa6d074", size = 7730953, upload-time = "2026-02-06T09:55:19.545Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/6e/8052e3a28eb6a820c372b2eb4b5e32d195c661e137d3eca94d534a4cfd8a/grpcio-1.78.0-cp311-cp311-win32.whl", hash = "sha256:6092beabe1966a3229f599d7088b38dfc8ffa1608b5b5cdda31e591e6500f856", size = 4076503, upload-time = "2026-02-06T09:55:21.521Z" },
+ { url = "https://files.pythonhosted.org/packages/08/62/f22c98c5265dfad327251fa2f840b591b1df5f5e15d88b19c18c86965b27/grpcio-1.78.0-cp311-cp311-win_amd64.whl", hash = "sha256:1afa62af6e23f88629f2b29ec9e52ec7c65a7176c1e0a83292b93c76ca882558", size = 4799767, upload-time = "2026-02-06T09:55:24.107Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" },
+ { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" },
+ { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" },
+ { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" },
+ { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" },
+ { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143, upload-time = "2026-02-06T09:55:52.035Z" },
+ { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926, upload-time = "2026-02-06T09:55:55.494Z" },
+ { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628, upload-time = "2026-02-06T09:55:58.533Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574, upload-time = "2026-02-06T09:56:01.786Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639, upload-time = "2026-02-06T09:56:04.529Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838, upload-time = "2026-02-06T09:56:08.397Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878, upload-time = "2026-02-06T09:56:10.914Z" },
+ { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412, upload-time = "2026-02-06T09:56:13.593Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899, upload-time = "2026-02-06T09:56:15.601Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" },
+ { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" },
+ { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" },
+ { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" },
+ { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" },
+ { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" },
+ { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" },
+]
+
+[[package]]
+name = "grpcio-tools"
+version = "1.78.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "grpcio" },
+ { name = "protobuf" },
+ { name = "setuptools" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8b/d1/cbefe328653f746fd319c4377836a25ba64226e41c6a1d7d5cdbc87a459f/grpcio_tools-1.78.0.tar.gz", hash = "sha256:4b0dd86560274316e155d925158276f8564508193088bc43e20d3f5dff956b2b", size = 5393026, upload-time = "2026-02-06T09:59:59.53Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5e/70/2118a814a62ab205c905d221064bc09021db83fceeb84764d35c00f0f633/grpcio_tools-1.78.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:ea64e38d1caa2b8468b08cb193f5a091d169b6dbfe1c7dac37d746651ab9d84e", size = 2545568, upload-time = "2026-02-06T09:57:30.308Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/a9/68134839dd1a00f964185ead103646d6dd6a396b92ed264eaf521431b793/grpcio_tools-1.78.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:4003fcd5cbb5d578b06176fd45883a72a8f9203152149b7c680ce28653ad9e3a", size = 5708704, upload-time = "2026-02-06T09:57:33.512Z" },
+ { url = "https://files.pythonhosted.org/packages/36/1b/b6135aa9534e22051c53e5b9c0853d18024a41c50aaff464b7b47c1ed379/grpcio_tools-1.78.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe6b0081775394c61ec633c9ff5dbc18337100eabb2e946b5c83967fe43b2748", size = 2591905, upload-time = "2026-02-06T09:57:35.338Z" },
+ { url = "https://files.pythonhosted.org/packages/41/2b/6380df1390d62b1d18ae18d4d790115abf4997fa29498aa50ba644ecb9d8/grpcio_tools-1.78.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:7e989ad2cd93db52d7f1a643ecaa156ac55bf0484f1007b485979ce8aef62022", size = 2905271, upload-time = "2026-02-06T09:57:37.932Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/07/9b369f37c8f4956b68778c044d57390a8f0f3b1cca590018809e75a4fce2/grpcio_tools-1.78.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b874991797e96c41a37e563236c3317ed41b915eff25b292b202d6277d30da85", size = 2656234, upload-time = "2026-02-06T09:57:41.157Z" },
+ { url = "https://files.pythonhosted.org/packages/51/61/40eee40e7a54f775a0d4117536532713606b6b177fff5e327f33ad18746e/grpcio_tools-1.78.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:daa8c288b728228377aaf758925692fc6068939d9fa32f92ca13dedcbeb41f33", size = 3105770, upload-time = "2026-02-06T09:57:43.373Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/ac/81ee4b728e70e8ba66a589f86469925ead02ed6f8973434e4a52e3576148/grpcio_tools-1.78.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:87e648759b06133199f4bc0c0053e3819f4ec3b900dc399e1097b6065db998b5", size = 3654896, upload-time = "2026-02-06T09:57:45.402Z" },
+ { url = "https://files.pythonhosted.org/packages/be/b9/facb3430ee427c800bb1e39588c85685677ea649491d6e0874bd9f3a1c0e/grpcio_tools-1.78.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f3d3ced52bfe39eba3d24f5a8fab4e12d071959384861b41f0c52ca5399d6920", size = 3322529, upload-time = "2026-02-06T09:57:47.292Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/de/d7a011df9abfed8c30f0d2077b0562a6e3edc57cb3e5514718e2a81f370a/grpcio_tools-1.78.0-cp310-cp310-win32.whl", hash = "sha256:4bb6ed690d417b821808796221bde079377dff98fdc850ac157ad2f26cda7a36", size = 993518, upload-time = "2026-02-06T09:57:48.836Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/5e/f7f60c3ae2281c6b438c3a8455f4a5d5d2e677cf20207864cbee3763da22/grpcio_tools-1.78.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c676d8342fd53bd85a5d5f0d070cd785f93bc040510014708ede6fcb32fada1", size = 1158505, upload-time = "2026-02-06T09:57:50.633Z" },
+ { url = "https://files.pythonhosted.org/packages/75/78/280184d19242ed6762bf453c47a70b869b3c5c72a24dc5bf2bf43909faa3/grpcio_tools-1.78.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:6a8b8b7b49f319d29dbcf507f62984fa382d1d10437d75c3f26db5f09c4ac0af", size = 2545904, upload-time = "2026-02-06T09:57:52.769Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/51/3c46dea5113f68fe879961cae62d34bb7a3c308a774301b45d614952ee98/grpcio_tools-1.78.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:d62cf3b68372b0c6d722a6165db41b976869811abeabc19c8522182978d8db10", size = 5709078, upload-time = "2026-02-06T09:57:56.389Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/2c/dc1ae9ec53182c96d56dfcbf3bcd3e55a8952ad508b188c75bf5fc8993d4/grpcio_tools-1.78.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fa9056742efeaf89d5fe14198af71e5cbc4fbf155d547b89507e19d6025906c6", size = 2591744, upload-time = "2026-02-06T09:57:58.341Z" },
+ { url = "https://files.pythonhosted.org/packages/04/63/9b53fc9a9151dd24386785171a4191ee7cb5afb4d983b6a6a87408f41b28/grpcio_tools-1.78.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e3191af125dcb705aa6bc3856ba81ba99b94121c1b6ebee152e66ea084672831", size = 2905113, upload-time = "2026-02-06T09:58:00.38Z" },
+ { url = "https://files.pythonhosted.org/packages/96/b2/0ad8d789f3a2a00893131c140865605fa91671a6e6fcf9da659e1fabba10/grpcio_tools-1.78.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:283239ddbb67ae83fac111c61b25d8527a1dbd355b377cbc8383b79f1329944d", size = 2656436, upload-time = "2026-02-06T09:58:03.038Z" },
+ { url = "https://files.pythonhosted.org/packages/09/4d/580f47ce2fc61b093ade747b378595f51b4f59972dd39949f7444b464a03/grpcio_tools-1.78.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ac977508c0db15301ef36d6c79769ec1a6cc4e3bc75735afca7fe7e360cead3a", size = 3106128, upload-time = "2026-02-06T09:58:05.064Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/29/d83b2d89f8d10e438bad36b1eb29356510fb97e81e6a608b22ae1890e8e6/grpcio_tools-1.78.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4ff605e25652a0bd13aa8a73a09bc48669c68170902f5d2bf1468a57d5e78771", size = 3654953, upload-time = "2026-02-06T09:58:07.15Z" },
+ { url = "https://files.pythonhosted.org/packages/08/71/917ce85633311e54fefd7e6eb1224fb780ef317a4d092766f5630c3fc419/grpcio_tools-1.78.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0197d7b561c79be78ab93d0fe2836c8def470683df594bae3ac89dd8e5c821b2", size = 3322630, upload-time = "2026-02-06T09:58:10.305Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/55/3fbf6b26ab46fc79e1e6f7f4e0993cf540263dad639290299fad374a0829/grpcio_tools-1.78.0-cp311-cp311-win32.whl", hash = "sha256:28f71f591f7f39555863ced84fcc209cbf4454e85ef957232f43271ee99af577", size = 993804, upload-time = "2026-02-06T09:58:13.698Z" },
+ { url = "https://files.pythonhosted.org/packages/73/86/4affe006d9e1e9e1c6653d6aafe2f8b9188acb2b563cd8ed3a2c7c0e8aec/grpcio_tools-1.78.0-cp311-cp311-win_amd64.whl", hash = "sha256:5a6de495dabf86a3b40b9a7492994e1232b077af9d63080811838b781abbe4e8", size = 1158566, upload-time = "2026-02-06T09:58:15.721Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/ae/5b1fa5dd8d560a6925aa52de0de8731d319f121c276e35b9b2af7cc220a2/grpcio_tools-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:9eb122da57d4cad7d339fc75483116f0113af99e8d2c67f3ef9cae7501d806e4", size = 2546823, upload-time = "2026-02-06T09:58:17.944Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/ed/d33ccf7fa701512efea7e7e23333b748848a123e9d3bbafde4e126784546/grpcio_tools-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d0c501b8249940b886420e6935045c44cb818fa6f265f4c2b97d5cff9cb5e796", size = 5706776, upload-time = "2026-02-06T09:58:20.944Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/69/4285583f40b37af28277fc6b867d636e3b10e1b6a7ebd29391a856e1279b/grpcio_tools-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:77e5aa2d2a7268d55b1b113f958264681ef1994c970f69d48db7d4683d040f57", size = 2593972, upload-time = "2026-02-06T09:58:23.29Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/eb/ecc1885bd6b3147f0a1b7dff5565cab72f01c8f8aa458f682a1c77a9fb08/grpcio_tools-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8e3c0b0e6ba5275322ba29a97bf890565a55f129f99a21b121145e9e93a22525", size = 2905531, upload-time = "2026-02-06T09:58:25.406Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/a9/511d0040ced66960ca10ba0f082d6b2d2ee6dd61837b1709636fdd8e23b4/grpcio_tools-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:975d4cb48694e20ebd78e1643e5f1cd94cdb6a3d38e677a8e84ae43665aa4790", size = 2656909, upload-time = "2026-02-06T09:58:28.022Z" },
+ { url = "https://files.pythonhosted.org/packages/06/a3/3d2c707e7dee8df842c96fbb24feb2747e506e39f4a81b661def7fed107c/grpcio_tools-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:553ff18c5d52807dedecf25045ae70bad7a3dbba0b27a9a3cdd9bcf0a1b7baec", size = 3109778, upload-time = "2026-02-06T09:58:30.091Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/4b/646811ba241bf05da1f0dc6f25764f1c837f78f75b4485a4210c84b79eae/grpcio_tools-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8c7f5e4af5a84d2e96c862b1a65e958a538237e268d5f8203a3a784340975b51", size = 3658763, upload-time = "2026-02-06T09:58:32.875Z" },
+ { url = "https://files.pythonhosted.org/packages/45/de/0a5ef3b3e79d1011375f5580dfee3a9c1ccb96c5f5d1c74c8cee777a2483/grpcio_tools-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:96183e2b44afc3f9a761e9d0f985c3b44e03e8bb98e626241a6cbfb3b6f7e88f", size = 3325116, upload-time = "2026-02-06T09:58:34.894Z" },
+ { url = "https://files.pythonhosted.org/packages/95/d2/6391b241ad571bc3e71d63f957c0b1860f0c47932d03c7f300028880f9b8/grpcio_tools-1.78.0-cp312-cp312-win32.whl", hash = "sha256:2250e8424c565a88573f7dc10659a0b92802e68c2a1d57e41872c9b88ccea7a6", size = 993493, upload-time = "2026-02-06T09:58:37.242Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/8f/7d0d3a39ecad76ccc136be28274daa660569b244fa7d7d0bbb24d68e5ece/grpcio_tools-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:217d1fa29de14d9c567d616ead7cb0fef33cde36010edff5a9390b00d52e5094", size = 1158423, upload-time = "2026-02-06T09:58:40.072Z" },
+ { url = "https://files.pythonhosted.org/packages/53/ce/17311fb77530420e2f441e916b347515133e83d21cd6cc77be04ce093d5b/grpcio_tools-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:2d6de1cc23bdc1baafc23e201b1e48c617b8c1418b4d8e34cebf72141676e5fb", size = 2546284, upload-time = "2026-02-06T09:58:43.073Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/d3/79e101483115f0e78223397daef71751b75eba7e92a32060c10aae11ca64/grpcio_tools-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2afeaad88040894c76656202ff832cb151bceb05c0e6907e539d129188b1e456", size = 5705653, upload-time = "2026-02-06T09:58:45.533Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/a7/52fa3ccb39ceeee6adc010056eadfbca8198651c113e418dafebbdf2b306/grpcio_tools-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:33cc593735c93c03d63efe7a8ba25f3c66f16c52f0651910712490244facad72", size = 2592788, upload-time = "2026-02-06T09:58:48.918Z" },
+ { url = "https://files.pythonhosted.org/packages/68/08/682ff6bb548225513d73dc9403742d8975439d7469c673bc534b9bbc83a7/grpcio_tools-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2921d7989c4d83b71f03130ab415fa4d66e6693b8b8a1fcbb7a1c67cff19b812", size = 2905157, upload-time = "2026-02-06T09:58:51.478Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/66/264f3836a96423b7018e5ada79d62576a6401f6da4e1f4975b18b2be1265/grpcio_tools-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6a0df438e82c804c7b95e3f311c97c2f876dcc36376488d5b736b7bcf5a9b45", size = 2656166, upload-time = "2026-02-06T09:58:54.117Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/6b/f108276611522e03e98386b668cc7e575eff6952f2db9caa15b2a3b3e883/grpcio_tools-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e9c6070a9500798225191ef25d0055a15d2c01c9c8f2ee7b681fffa99c98c822", size = 3109110, upload-time = "2026-02-06T09:58:56.891Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/c7/cf048dbcd64b3396b3c860a2ffbcc67a8f8c87e736aaa74c2e505a7eee4c/grpcio_tools-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:394e8b57d85370a62e5b0a4d64c96fcf7568345c345d8590c821814d227ecf1d", size = 3657863, upload-time = "2026-02-06T09:58:59.176Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/37/e2736912c8fda57e2e57a66ea5e0bc8eb9a5fb7ded00e866ad22d50afb08/grpcio_tools-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3ef700293ab375e111a2909d87434ed0a0b086adf0ce67a8d9cf12ea7765e63", size = 3324748, upload-time = "2026-02-06T09:59:01.242Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/5d/726abc75bb5bfc2841e88ea05896e42f51ca7c30cb56da5c5b63058b3867/grpcio_tools-1.78.0-cp313-cp313-win32.whl", hash = "sha256:6993b960fec43a8d840ee5dc20247ef206c1a19587ea49fe5e6cc3d2a09c1585", size = 993074, upload-time = "2026-02-06T09:59:03.085Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/68/91b400bb360faf9b177ffb5540ec1c4d06ca923691ddf0f79e2c9683f4da/grpcio_tools-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:275ce3c2978842a8cf9dd88dce954e836e590cf7029649ad5d1145b779039ed5", size = 1158185, upload-time = "2026-02-06T09:59:05.036Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/5e/278f3831c8d56bae02e3acc570465648eccf0a6bbedcb1733789ac966803/grpcio_tools-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:8b080d0d072e6032708a3a91731b808074d7ab02ca8fb9847b6a011fdce64cd9", size = 2546270, upload-time = "2026-02-06T09:59:07.426Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/d9/68582f2952b914b60dddc18a2e3f9c6f09af9372b6f6120d6cf3ec7f8b4e/grpcio_tools-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8c0ad8f8f133145cd7008b49cb611a5c6a9d89ab276c28afa17050516e801f79", size = 5705731, upload-time = "2026-02-06T09:59:09.856Z" },
+ { url = "https://files.pythonhosted.org/packages/70/68/feb0f9a48818ee1df1e8b644069379a1e6ef5447b9b347c24e96fd258e5d/grpcio_tools-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2f8ea092a7de74c6359335d36f0674d939a3c7e1a550f4c2c9e80e0226de8fe4", size = 2593896, upload-time = "2026-02-06T09:59:12.23Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/08/a430d8d06e1b8d33f3e48d3f0cc28236723af2f35e37bd5c8db05df6c3aa/grpcio_tools-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:da422985e0cac822b41822f43429c19ecb27c81ffe3126d0b74e77edec452608", size = 2905298, upload-time = "2026-02-06T09:59:14.458Z" },
+ { url = "https://files.pythonhosted.org/packages/71/0a/348c36a3eae101ca0c090c9c3bc96f2179adf59ee0c9262d11cdc7bfe7db/grpcio_tools-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4fab1faa3fbcb246263e68da7a8177d73772283f9db063fb8008517480888d26", size = 2656186, upload-time = "2026-02-06T09:59:16.949Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/3f/18219f331536fad4af6207ade04142292faa77b5cb4f4463787988963df8/grpcio_tools-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dd9c094f73f734becae3f20f27d4944d3cd8fb68db7338ee6c58e62fc5c3d99f", size = 3109859, upload-time = "2026-02-06T09:59:19.202Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/d9/341ea20a44c8e5a3a18acc820b65014c2e3ea5b4f32a53d14864bcd236bc/grpcio_tools-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2ed51ce6b833068f6c580b73193fc2ec16468e6bc18354bc2f83a58721195a58", size = 3657915, upload-time = "2026-02-06T09:59:21.839Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/f4/5978b0f91611a64371424c109dd0027b247e5b39260abad2eaee66b6aa37/grpcio_tools-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:05803a5cdafe77c8bdf36aa660ad7a6a1d9e49bc59ce45c1bade2a4698826599", size = 3324724, upload-time = "2026-02-06T09:59:24.402Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/80/96a324dba99cfbd20e291baf0b0ae719dbb62b76178c5ce6c788e7331cb1/grpcio_tools-1.78.0-cp314-cp314-win32.whl", hash = "sha256:f7c722e9ce6f11149ac5bddd5056e70aaccfd8168e74e9d34d8b8b588c3f5c7c", size = 1015505, upload-time = "2026-02-06T09:59:26.3Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/d1/909e6a05bfd44d46327dc4b8a78beb2bae4fb245ffab2772e350081aaf7e/grpcio_tools-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d58ade518b546120ec8f0a8e006fc8076ae5df151250ebd7e82e9b5e152c229", size = 1190196, upload-time = "2026-02-06T09:59:28.359Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
+]
+
+[[package]]
+name = "importlib-metadata"
+version = "8.7.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "zipp" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
+]
+
+[[package]]
+name = "opentelemetry-api"
+version = "1.40.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "importlib-metadata" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" },
+]
+
+[[package]]
+name = "opentelemetry-exporter-otlp-proto-common"
+version = "1.40.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "opentelemetry-proto" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" },
+]
+
+[[package]]
+name = "opentelemetry-exporter-otlp-proto-grpc"
+version = "1.40.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "googleapis-common-protos" },
+ { name = "grpcio" },
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-exporter-otlp-proto-common" },
+ { name = "opentelemetry-proto" },
+ { name = "opentelemetry-sdk" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8f/7f/b9e60435cfcc7590fa87436edad6822240dddbc184643a2a005301cc31f4/opentelemetry_exporter_otlp_proto_grpc-1.40.0.tar.gz", hash = "sha256:bd4015183e40b635b3dab8da528b27161ba83bf4ef545776b196f0fb4ec47740", size = 25759, upload-time = "2026-03-04T14:17:24.4Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/96/6f/7ee0980afcbdcd2d40362da16f7f9796bd083bf7f0b8e038abfbc0300f5d/opentelemetry_exporter_otlp_proto_grpc-1.40.0-py3-none-any.whl", hash = "sha256:2aa0ca53483fe0cf6405087a7491472b70335bc5c7944378a0a8e72e86995c52", size = 20304, upload-time = "2026-03-04T14:17:05.942Z" },
+]
+
+[[package]]
+name = "opentelemetry-exporter-otlp-proto-http"
+version = "1.40.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "googleapis-common-protos" },
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-exporter-otlp-proto-common" },
+ { name = "opentelemetry-proto" },
+ { name = "opentelemetry-sdk" },
+ { name = "requests" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" },
+]
+
+[[package]]
+name = "opentelemetry-proto"
+version = "1.40.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "protobuf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" },
+]
+
+[[package]]
+name = "opentelemetry-sdk"
+version = "1.40.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-semantic-conventions" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" },
+]
+
+[[package]]
+name = "opentelemetry-semantic-conventions"
+version = "0.61b0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "opentelemetry-api" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "26.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[package]]
+name = "protobuf"
+version = "6.33.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" },
+ { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" },
+ { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "9.0.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "iniconfig" },
+ { name = "packaging" },
+ { name = "pluggy" },
+ { name = "pygments" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
+]
+
+[[package]]
+name = "requests"
+version = "2.32.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
+]
+
+[[package]]
+name = "setuptools"
+version = "82.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" },
+]
+
+[[package]]
+name = "sigil-sdk"
+version = "0.1.0"
+source = { editable = "." }
+dependencies = [
+ { name = "grpcio" },
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-exporter-otlp-proto-grpc" },
+ { name = "opentelemetry-exporter-otlp-proto-http" },
+ { name = "opentelemetry-proto" },
+ { name = "opentelemetry-sdk" },
+ { name = "protobuf" },
+]
+
+[package.optional-dependencies]
+dev = [
+ { name = "grpcio-tools" },
+ { name = "pytest" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "grpcio", specifier = ">=1.78.0" },
+ { name = "grpcio-tools", marker = "extra == 'dev'", specifier = ">=1.78.0" },
+ { name = "opentelemetry-api", specifier = ">=1.27.0" },
+ { name = "opentelemetry-exporter-otlp-proto-grpc", specifier = ">=1.27.0" },
+ { name = "opentelemetry-exporter-otlp-proto-http", specifier = ">=1.27.0" },
+ { name = "opentelemetry-proto", specifier = ">=1.27.0" },
+ { name = "opentelemetry-sdk", specifier = ">=1.27.0" },
+ { name = "protobuf", specifier = ">=6.31.1" },
+ { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.2.0" },
+]
+provides-extras = ["dev"]
+
+[[package]]
+name = "tomli"
+version = "2.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" },
+ { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" },
+ { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" },
+ { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" },
+ { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" },
+ { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" },
+ { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" },
+ { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" },
+ { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" },
+ { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" },
+ { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" },
+ { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" },
+ { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" },
+ { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" },
+ { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.6.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
+]
+
+[[package]]
+name = "zipp"
+version = "3.23.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" },
+]