feat: Python otel support, enriching spans, OpenAI Agents SDK example (#1839)

* D3 demo WIP

* Agent working and completing token successfully

* Sending OpenAI Agent SDK spans through to the platform now works

* A couple of perf tweaks for enriching events
This commit is contained in:
Eric Allam
2025-03-28 13:01:59 +00:00
committed by GitHub
parent 2a6d8251b2
commit f4ef008e2c
39 changed files with 1495 additions and 171 deletions
+28 -11
View File
@@ -27,6 +27,7 @@ import {
import { logger } from "~/services/logger.server";
import { trace, Tracer } from "@opentelemetry/api";
import { startSpan } from "./tracing.server";
import { enrichCreatableEvents } from "./utils/enrichCreatableEvents.server";
export type OTLPExporterConfig = {
batchSize: number;
@@ -54,14 +55,16 @@ class OTLPExporter {
return convertSpansToCreateableEvents(resourceSpan);
});
this.#logEventsVerbose(events);
const enrichedEvents = enrichCreatableEvents(events);
span.setAttribute("event_count", events.length);
this.#logEventsVerbose(enrichedEvents);
span.setAttribute("event_count", enrichedEvents.length);
if (immediate) {
await this._eventRepository.insertManyImmediate(events);
await this._eventRepository.insertManyImmediate(enrichedEvents);
} else {
await this._eventRepository.insertMany(events);
await this._eventRepository.insertMany(enrichedEvents);
}
return ExportTraceServiceResponse.create();
@@ -79,14 +82,16 @@ class OTLPExporter {
return convertLogsToCreateableEvents(resourceLog);
});
this.#logEventsVerbose(events);
const enrichedEvents = enrichCreatableEvents(events);
span.setAttribute("event_count", events.length);
this.#logEventsVerbose(enrichedEvents);
span.setAttribute("event_count", enrichedEvents.length);
if (immediate) {
await this._eventRepository.insertManyImmediate(events);
await this._eventRepository.insertManyImmediate(enrichedEvents);
} else {
await this._eventRepository.insertMany(events);
await this._eventRepository.insertMany(enrichedEvents);
}
return ExportLogsServiceResponse.create();
@@ -135,16 +140,28 @@ class OTLPExporter {
(attribute) => attribute.key === SemanticInternalAttributes.TRIGGER
);
if (!triggerAttribute) {
const executionEnvironmentAttribute = resourceSpan.resource?.attributes.find(
(attribute) => attribute.key === SemanticInternalAttributes.EXECUTION_ENVIRONMENT
);
if (!triggerAttribute && !executionEnvironmentAttribute) {
logger.debug("Skipping resource span without trigger attribute", {
attributes: resourceSpan.resource?.attributes,
spans: resourceSpan.scopeSpans.flatMap((scopeSpan) => scopeSpan.spans),
});
return;
return true; // go ahead and let this resource span through
}
return isBoolValue(triggerAttribute.value) ? triggerAttribute.value.boolValue : false;
const executionEnvironment = isStringValue(executionEnvironmentAttribute?.value)
? executionEnvironmentAttribute.value.stringValue
: undefined;
if (executionEnvironment === "trigger") {
return true; // go ahead and let this resource span through
}
return isBoolValue(triggerAttribute?.value) ? triggerAttribute.value.boolValue : false;
});
}
@@ -0,0 +1,67 @@
import type { CreatableEvent } from "../eventRepository.server";
export function enrichCreatableEvents(events: CreatableEvent[]) {
return events.map((event) => {
return enrichCreatableEvent(event);
});
}
function enrichCreatableEvent(event: CreatableEvent): CreatableEvent {
const message = formatPythonStyle(event.message, event.properties);
event.message = message;
event.style = enrichStyle(event);
return event;
}
function enrichStyle(event: CreatableEvent) {
const baseStyle = event.style ?? {};
const props = event.properties;
// Direct property access and early returns
// GenAI System check
const system = props["gen_ai.system"];
if (typeof system === "string") {
return { ...baseStyle, icon: `tabler-brand-${system}` };
}
// Agent workflow check
const name = props["name"];
if (typeof name === "string" && name.includes("Agent workflow")) {
return { ...baseStyle, icon: "tabler-brain" };
}
return baseStyle;
}
function repr(value: any): string {
if (typeof value === "string") {
return `'${value}'`;
}
return String(value);
}
function formatPythonStyle(template: string, values: Record<string, any>): string {
// Early return if template is too long
if (template.length >= 256) {
return template;
}
// Early return if no template variables present
if (!template.includes("{")) {
return template;
}
return template.replace(/\{([^}]+?)(?:!r)?\}/g, (match, key) => {
const hasRepr = match.endsWith("!r}");
const actualKey = hasRepr ? key : key;
const value = values[actualKey];
if (value === undefined) {
return match;
}
return hasRepr ? repr(value) : String(value);
});
}
+397
View File
@@ -0,0 +1,397 @@
import { describe, it, expect } from "vitest";
import { enrichCreatableEvents } from "../app/v3/utils/enrichCreatableEvents.server.js";
import {
RuntimeEnvironmentType,
TaskEventKind,
TaskEventLevel,
TaskEventStatus,
} from "@trigger.dev/database";
import { SemanticInternalAttributes } from "@trigger.dev/core/v3";
describe("OTLPExporter", () => {
describe("enrichCreatableEvents", () => {
it("should enrich creatable events that have template strings in the message", () => {
const events = [
{
traceId: "4e184936f7a0ef5af428a29b4d47da9d",
spanId: "1be4ebd8c78e43e9",
parentId: "f1a6d77fd5872bbe",
message: "Responses API with {gen_ai.request.model!r}",
isPartial: false,
isError: false,
kind: TaskEventKind.INTERNAL,
level: TaskEventLevel.TRACE,
status: TaskEventStatus.UNSET,
startTime: BigInt(1743103947959190000),
links: [],
events: [],
duration: 2931702000,
properties: {
"code.filepath": "src/trigger/python/agent.py",
"code.lineno": 171,
model_settings:
'{"temperature":null,"top_p":null,"frequency_penalty":null,"presence_penalty":null,"tool_choice":null,"parallel_tool_calls":false,"truncation":null,"max_tokens":null}',
"gen_ai.request.model": "gpt-4o",
"logfire.msg_template": "Responses API with {gen_ai.request.model!r}",
"logfire.span_type": "span",
response_id: "resp_67e5a7cd046c8192b3bfb6c863057f60003e29fb266e3c7b",
"gen_ai.system": "openai",
"gen_ai.response.model": "gpt-4o-2024-08-06",
response:
'{"id":"resp_67e5a7cd046c8192b3bfb6c863057f60003e29fb266e3c7b","created_at":1743103949.0,"error":null,"incomplete_details":null,"instructions":"You are an expert at enriching contact information data.\\n Your task is to use web search to find additional information about a person\\n based on their basic contact details.\\n\\n Please find the basic information, company information, and social media information for the person.\\n\\n The input data is from an unspecified CSV dataset, but most likely a CSV dump from a CRM or other source like Mailchimp, etc.\\n\\n You\'ll receive the header row and a single row from the dataset to enrich, in their raw form.\\n \\n Only include information you are confident about from reliable sources.\\n Use null for fields where you cannot find reliable information.\\n ","metadata":{},"model":"gpt-4o-2024-08-06","object":"response","output":[{"arguments":"{\\"input\\":\\"Eric,eric@trigger.dev\\"}","call_id":"call_OPlvy1ubdx75L8AgGIfVTWlW","name":"basic_info_agent","type":"function_call","id":"fc_67e5a7ce1418819281d73995bf0ffcf9003e29fb266e3c7b","status":"completed"}],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"auto","tools":[{"name":"basic_info_agent","parameters":{"properties":{"input":{"title":"Input","type":"string"}},"required":["input"],"title":"basic_info_agent_args","type":"object","additionalProperties":false},"strict":true,"type":"function","description":"Extract basic information from a person\'s contact details"},{"name":"company_info_agent","parameters":{"properties":{"input":{"title":"Input","type":"string"}},"required":["input"],"title":"company_info_agent_args","type":"object","additionalProperties":false},"strict":true,"type":"function","description":"Extract company information from a person\'s contact details"},{"name":"social_info_agent","parameters":{"properties":{"input":{"title":"Input","type":"string"}},"required":["input"],"title":"social_info_agent_args","type":"object","additionalProperties":false},"strict":true,"type":"function","description":"Extract social media information from a person\'s contact details"}],"top_p":1.0,"max_output_tokens":null,"previous_response_id":null,"reasoning":{"effort":null,"generate_summary":null},"status":"completed","text":{"format":{"schema_":{"$defs":{"BasicInfo":{"properties":{"name":{"description":"The name of the row","title":"Name","type":"string"},"email":{"description":"The email of the row","title":"Email","type":"string"},"firstName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The first name of the row","title":"Firstname"},"lastName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The last name of the row","title":"Lastname"},"preferredName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The preferred name of the row","title":"Preferredname"}},"required":["name","email","firstName","lastName","preferredName"],"title":"BasicInfo","type":"object","additionalProperties":false},"CompanyInfo":{"properties":{"name":{"description":"The name of the company","title":"Name","type":"string"},"industry":{"description":"The industry of the company","title":"Industry","type":"string"}},"required":["name","industry"],"title":"CompanyInfo","type":"object","additionalProperties":false},"SocialInfo":{"properties":{"twitter":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The Twitter username of the person","title":"Twitter"},"linkedin":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The LinkedIn username of the person","title":"Linkedin"},"facebook":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The Facebook username of the person","title":"Facebook"},"instagram":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The Instagram username of the person","title":"Instagram"}},"title":"SocialInfo","type":"object","additionalProperties":false,"required":["twitter","linkedin","facebook","instagram"]}},"properties":{"basicInfo":{"$ref":"#/$defs/BasicInfo"},"companyInfo":{"$ref":"#/$defs/CompanyInfo"},"socialInfo":{"$ref":"#/$defs/SocialInfo"}},"required":["basicInfo","companyInfo","socialInfo"],"title":"RowEnrichmentResult","type":"object","additionalProperties":false},"type":"json_schema","description":null,"name":"final_output","strict":true}},"truncation":"disabled","usage":{"input_tokens":702,"input_tokens_details":{"cached_tokens":0},"output_tokens":22,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":724},"user":null,"store":true}',
"gen_ai.operation.name": "chat",
raw_input:
'[{"content":"\\n Header row: Name,Email\\n Row to enrich: Eric,eric@trigger.dev\\n CSV URL: https://example.com/import.csv\\n ","role":"user"}]',
events:
'[{"event.name":"gen_ai.system.message","content":"You are an expert at enriching contact information data.\\n Your task is to use web search to find additional information about a person\\n based on their basic contact details.\\n\\n Please find the basic information, company information, and social media information for the person.\\n\\n The input data is from an unspecified CSV dataset, but most likely a CSV dump from a CRM or other source like Mailchimp, etc.\\n\\n You\'ll receive the header row and a single row from the dataset to enrich, in their raw form.\\n \\n Only include information you are confident about from reliable sources.\\n Use null for fields where you cannot find reliable information.\\n ","role":"system"},{"event.name":"gen_ai.user.message","content":"\\n Header row: Name,Email\\n Row to enrich: Eric,eric@trigger.dev\\n CSV URL: https://example.com/import.csv\\n ","role":"user"},{"event.name":"gen_ai.choice","index":0,"message":{"role":"assistant","tool_calls":[{"id":"call_OPlvy1ubdx75L8AgGIfVTWlW","type":"function","function":{"name":"basic_info_agent","arguments":"{\\"input\\":\\"Eric,eric@trigger.dev\\"}"}}]}}]',
"gen_ai.usage.input_tokens": 702,
"gen_ai.usage.output_tokens": 22,
"logfire.msg": "Responses API with 'gpt-4o'",
},
payloadType: "application/json",
metadata: {
"service.instance.id": "eae3f05bfa924058b1aa79c6decd79fe",
},
serviceName: "d3-demo",
serviceNamespace: "unknown",
environmentId: "cm8r871mw0002dy8n6ec3y0i2",
environmentType: RuntimeEnvironmentType.DEVELOPMENT,
organizationId: "cm5ohng6o0002dyuzvr0qq4xo",
projectId: "cm8r871lq0000dy8nhljain0w",
projectRef: "proj_wkbbtayxrmeyqhankehb",
runId: "run_cm8rr2zw50000efdyyq0376m5",
runIsTest: false,
attemptId: "deprecated",
taskSlug: "row-agent-runner",
taskPath: "src/trigger/d3Demo.ts",
taskExportName: "@deprecated",
workerId: "unmanaged",
workerVersion: "20250327.54",
queueId: "queue_pn4256pg6c2wc9rro3o7i",
queueName: "task/row-agent-runner",
batchId: "undefined",
idempotencyKey: "undefined",
machinePreset: "small-1x",
style: {},
output: {},
payload: {},
},
];
const $events = enrichCreatableEvents(events);
expect($events).toBeDefined();
const event = $events[0];
expect(event.message).toBe("Responses API with 'gpt-4o'");
expect(event.style).toEqual({
icon: "tabler-brand-openai",
});
});
it("should enrich creatable events that have template strings without the !r in the message", () => {
const events = [
{
traceId: "4e184936f7a0ef5af428a29b4d47da9d",
spanId: "1be4ebd8c78e43e9",
parentId: "f1a6d77fd5872bbe",
message: "Responses API with {gen_ai.request.model}",
isPartial: false,
isError: false,
kind: TaskEventKind.INTERNAL,
level: TaskEventLevel.TRACE,
status: TaskEventStatus.UNSET,
startTime: BigInt(1743103947959190000),
links: [],
events: [],
duration: 2931702000,
properties: {
"code.filepath": "src/trigger/python/agent.py",
"code.lineno": 171,
model_settings:
'{"temperature":null,"top_p":null,"frequency_penalty":null,"presence_penalty":null,"tool_choice":null,"parallel_tool_calls":false,"truncation":null,"max_tokens":null}',
"gen_ai.request.model": "gpt-4o",
"logfire.msg_template": "Responses API with {gen_ai.request.model}",
"logfire.span_type": "span",
response_id: "resp_67e5a7cd046c8192b3bfb6c863057f60003e29fb266e3c7b",
"gen_ai.system": "openai",
"gen_ai.response.model": "gpt-4o-2024-08-06",
response:
'{"id":"resp_67e5a7cd046c8192b3bfb6c863057f60003e29fb266e3c7b","created_at":1743103949.0,"error":null,"incomplete_details":null,"instructions":"You are an expert at enriching contact information data.\\n Your task is to use web search to find additional information about a person\\n based on their basic contact details.\\n\\n Please find the basic information, company information, and social media information for the person.\\n\\n The input data is from an unspecified CSV dataset, but most likely a CSV dump from a CRM or other source like Mailchimp, etc.\\n\\n You\'ll receive the header row and a single row from the dataset to enrich, in their raw form.\\n \\n Only include information you are confident about from reliable sources.\\n Use null for fields where you cannot find reliable information.\\n ","metadata":{},"model":"gpt-4o-2024-08-06","object":"response","output":[{"arguments":"{\\"input\\":\\"Eric,eric@trigger.dev\\"}","call_id":"call_OPlvy1ubdx75L8AgGIfVTWlW","name":"basic_info_agent","type":"function_call","id":"fc_67e5a7ce1418819281d73995bf0ffcf9003e29fb266e3c7b","status":"completed"}],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"auto","tools":[{"name":"basic_info_agent","parameters":{"properties":{"input":{"title":"Input","type":"string"}},"required":["input"],"title":"basic_info_agent_args","type":"object","additionalProperties":false},"strict":true,"type":"function","description":"Extract basic information from a person\'s contact details"},{"name":"company_info_agent","parameters":{"properties":{"input":{"title":"Input","type":"string"}},"required":["input"],"title":"company_info_agent_args","type":"object","additionalProperties":false},"strict":true,"type":"function","description":"Extract company information from a person\'s contact details"},{"name":"social_info_agent","parameters":{"properties":{"input":{"title":"Input","type":"string"}},"required":["input"],"title":"social_info_agent_args","type":"object","additionalProperties":false},"strict":true,"type":"function","description":"Extract social media information from a person\'s contact details"}],"top_p":1.0,"max_output_tokens":null,"previous_response_id":null,"reasoning":{"effort":null,"generate_summary":null},"status":"completed","text":{"format":{"schema_":{"$defs":{"BasicInfo":{"properties":{"name":{"description":"The name of the row","title":"Name","type":"string"},"email":{"description":"The email of the row","title":"Email","type":"string"},"firstName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The first name of the row","title":"Firstname"},"lastName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The last name of the row","title":"Lastname"},"preferredName":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The preferred name of the row","title":"Preferredname"}},"required":["name","email","firstName","lastName","preferredName"],"title":"BasicInfo","type":"object","additionalProperties":false},"CompanyInfo":{"properties":{"name":{"description":"The name of the company","title":"Name","type":"string"},"industry":{"description":"The industry of the company","title":"Industry","type":"string"}},"required":["name","industry"],"title":"CompanyInfo","type":"object","additionalProperties":false},"SocialInfo":{"properties":{"twitter":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The Twitter username of the person","title":"Twitter"},"linkedin":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The LinkedIn username of the person","title":"Linkedin"},"facebook":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The Facebook username of the person","title":"Facebook"},"instagram":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The Instagram username of the person","title":"Instagram"}},"title":"SocialInfo","type":"object","additionalProperties":false,"required":["twitter","linkedin","facebook","instagram"]}},"properties":{"basicInfo":{"$ref":"#/$defs/BasicInfo"},"companyInfo":{"$ref":"#/$defs/CompanyInfo"},"socialInfo":{"$ref":"#/$defs/SocialInfo"}},"required":["basicInfo","companyInfo","socialInfo"],"title":"RowEnrichmentResult","type":"object","additionalProperties":false},"type":"json_schema","description":null,"name":"final_output","strict":true}},"truncation":"disabled","usage":{"input_tokens":702,"input_tokens_details":{"cached_tokens":0},"output_tokens":22,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":724},"user":null,"store":true}',
"gen_ai.operation.name": "chat",
raw_input:
'[{"content":"\\n Header row: Name,Email\\n Row to enrich: Eric,eric@trigger.dev\\n CSV URL: https://example.com/import.csv\\n ","role":"user"}]',
events:
'[{"event.name":"gen_ai.system.message","content":"You are an expert at enriching contact information data.\\n Your task is to use web search to find additional information about a person\\n based on their basic contact details.\\n\\n Please find the basic information, company information, and social media information for the person.\\n\\n The input data is from an unspecified CSV dataset, but most likely a CSV dump from a CRM or other source like Mailchimp, etc.\\n\\n You\'ll receive the header row and a single row from the dataset to enrich, in their raw form.\\n \\n Only include information you are confident about from reliable sources.\\n Use null for fields where you cannot find reliable information.\\n ","role":"system"},{"event.name":"gen_ai.user.message","content":"\\n Header row: Name,Email\\n Row to enrich: Eric,eric@trigger.dev\\n CSV URL: https://example.com/import.csv\\n ","role":"user"},{"event.name":"gen_ai.choice","index":0,"message":{"role":"assistant","tool_calls":[{"id":"call_OPlvy1ubdx75L8AgGIfVTWlW","type":"function","function":{"name":"basic_info_agent","arguments":"{\\"input\\":\\"Eric,eric@trigger.dev\\"}"}}]}}]',
"gen_ai.usage.input_tokens": 702,
"gen_ai.usage.output_tokens": 22,
"logfire.msg": "Responses API with 'gpt-4o'",
},
payloadType: "application/json",
metadata: {
"service.instance.id": "eae3f05bfa924058b1aa79c6decd79fe",
},
serviceName: "d3-demo",
serviceNamespace: "unknown",
environmentId: "cm8r871mw0002dy8n6ec3y0i2",
environmentType: RuntimeEnvironmentType.DEVELOPMENT,
organizationId: "cm5ohng6o0002dyuzvr0qq4xo",
projectId: "cm8r871lq0000dy8nhljain0w",
projectRef: "proj_wkbbtayxrmeyqhankehb",
runId: "run_cm8rr2zw50000efdyyq0376m5",
runIsTest: false,
attemptId: "deprecated",
taskSlug: "row-agent-runner",
taskPath: "src/trigger/d3Demo.ts",
taskExportName: "@deprecated",
workerId: "unmanaged",
workerVersion: "20250327.54",
queueId: "queue_pn4256pg6c2wc9rro3o7i",
queueName: "task/row-agent-runner",
batchId: "undefined",
idempotencyKey: "undefined",
machinePreset: "small-1x",
style: {},
output: {},
payload: {},
},
];
const $events = enrichCreatableEvents(events);
expect($events).toBeDefined();
const event = $events[0];
expect(event.message).toBe("Responses API with gpt-4o");
expect(event.style).toEqual({
icon: "tabler-brand-openai",
});
});
it("should handle missing properties gracefully", () => {
const events = [
{
message: "Responses API with {missing.property!r}",
properties: {},
traceId: "test",
spanId: "test",
isPartial: false,
isError: false,
kind: TaskEventKind.INTERNAL,
level: TaskEventLevel.TRACE,
status: TaskEventStatus.UNSET,
startTime: BigInt(1),
style: {},
serviceName: "test",
environmentType: RuntimeEnvironmentType.DEVELOPMENT,
},
];
// @ts-expect-error
const $events = enrichCreatableEvents(events);
expect($events[0].message).toBe("Responses API with {missing.property!r}");
expect($events[0].style).toEqual({});
});
it("should handle multiple template variables in one message", () => {
const events = [
{
message: "Using {gen_ai.request.model!r} with temperature {gen_ai.request.temperature}",
properties: {
"gen_ai.request.model": "gpt-4",
"gen_ai.request.temperature": 0.7,
"gen_ai.system": "openai",
},
traceId: "test",
spanId: "test",
isPartial: false,
isError: false,
kind: TaskEventKind.INTERNAL,
level: TaskEventLevel.TRACE,
status: TaskEventStatus.UNSET,
startTime: BigInt(1),
style: {},
serviceName: "test",
environmentType: RuntimeEnvironmentType.DEVELOPMENT,
},
];
// @ts-expect-error
const $events = enrichCreatableEvents(events);
expect($events[0].message).toBe("Using 'gpt-4' with temperature 0.7");
expect($events[0].style).toEqual({
icon: "tabler-brand-openai",
});
});
it("should handle non-string property values", () => {
const events = [
{
message: "Count is {count!r} and enabled is {enabled!r}",
properties: {
count: 42,
enabled: true,
"gen_ai.system": "anthropic",
},
traceId: "test",
spanId: "test",
isPartial: false,
isError: false,
kind: TaskEventKind.INTERNAL,
level: TaskEventLevel.TRACE,
status: TaskEventStatus.UNSET,
startTime: BigInt(1),
style: {},
serviceName: "test",
environmentType: RuntimeEnvironmentType.DEVELOPMENT,
},
];
// @ts-expect-error
const $events = enrichCreatableEvents(events);
expect($events[0].message).toBe("Count is 42 and enabled is true");
expect($events[0].style).toEqual({
icon: "tabler-brand-anthropic",
});
});
it("should handle messages without any template variables", () => {
const events = [
{
message: "Plain message without variables",
properties: {
"gen_ai.system": "openai",
},
traceId: "test",
spanId: "test",
isPartial: false,
isError: false,
kind: TaskEventKind.INTERNAL,
level: TaskEventLevel.TRACE,
status: TaskEventStatus.UNSET,
startTime: BigInt(1),
style: {},
serviceName: "test",
environmentType: RuntimeEnvironmentType.DEVELOPMENT,
},
];
// @ts-expect-error
const $events = enrichCreatableEvents(events);
expect($events[0].message).toBe("Plain message without variables");
expect($events[0].style).toEqual({
icon: "tabler-brand-openai",
});
});
it("should handle style enrichment without gen_ai.system", () => {
const events = [
{
message: "Message without system",
properties: {
"some.other.property": "value",
},
traceId: "test",
spanId: "test",
isPartial: false,
isError: false,
kind: TaskEventKind.INTERNAL,
level: TaskEventLevel.TRACE,
status: TaskEventStatus.UNSET,
startTime: BigInt(1),
style: { existingStyle: "value" },
serviceName: "test",
environmentType: RuntimeEnvironmentType.DEVELOPMENT,
},
];
// @ts-expect-error
const $events = enrichCreatableEvents(events);
expect($events[0].style).toEqual({ existingStyle: "value" });
});
it("should apply the first matching style enricher", () => {
const events = [
{
message: "Agent workflow",
properties: {
"gen_ai.system": "openai",
name: "Agent workflow",
},
traceId: "test",
spanId: "test",
isPartial: false,
isError: false, // This would match the error enricher, but GenAI comes first
kind: TaskEventKind.INTERNAL,
level: TaskEventLevel.TRACE,
status: TaskEventStatus.UNSET,
startTime: BigInt(1),
style: { existingStyle: "value" },
serviceName: "test",
environmentType: RuntimeEnvironmentType.DEVELOPMENT,
},
];
// @ts-expect-error
const $events = enrichCreatableEvents(events);
expect($events[0].style).toEqual({
existingStyle: "value",
icon: "tabler-brand-openai", // GenAI enricher wins because it's first
});
});
it("should fall back to next enricher if first doesn't match", () => {
const events = [
{
message: "Agent workflow",
properties: {
"task.type": "background",
name: "Agent workflow",
},
traceId: "test",
spanId: "test",
isPartial: false,
isError: true,
kind: TaskEventKind.INTERNAL,
level: TaskEventLevel.TRACE,
status: TaskEventStatus.UNSET,
startTime: BigInt(1),
style: { existingStyle: "value" },
serviceName: "test",
environmentType: RuntimeEnvironmentType.DEVELOPMENT,
},
];
// @ts-expect-error
const $events = enrichCreatableEvents(events);
expect($events[0].style).toEqual({
existingStyle: "value",
icon: "tabler-brain",
});
});
it("should preserve existing style if no enricher matches", () => {
const events = [
{
message: "Test message",
properties: {},
traceId: "test",
spanId: "test",
isPartial: false,
isError: false,
kind: TaskEventKind.INTERNAL,
level: TaskEventLevel.TRACE,
status: TaskEventStatus.UNSET,
startTime: BigInt(1),
style: { existingStyle: "value" },
serviceName: "test",
environmentType: RuntimeEnvironmentType.DEVELOPMENT,
},
];
// @ts-expect-error
const $events = enrichCreatableEvents(events);
expect($events[0].style).toEqual({
existingStyle: "value",
});
});
});
});
+8 -1
View File
@@ -1,4 +1,4 @@
import { type Span, SpanStatusCode } from "@opentelemetry/api";
import { type Span, SpanStatusCode, context, propagation } from "@opentelemetry/api";
export function recordSpanException(span: Span, error: unknown) {
if (error instanceof Error) {
@@ -20,3 +20,10 @@ function sanitizeSpanError(error: Error) {
return sanitizedError;
}
export function carrierFromContext(): Record<string, string> {
const carrier = {};
propagation.inject(context.active(), carrier);
return carrier;
}
@@ -57,4 +57,5 @@ export const SemanticInternalAttributes = {
RATE_LIMIT_RESET: "response.rateLimit.reset",
SPAN_ATTEMPT: "$span.attempt",
METRIC_EVENTS: "$metrics.events",
EXECUTION_ENVIRONMENT: "exec_env",
};
+6 -1
View File
@@ -4,7 +4,12 @@ export { PreciseWallClock as DurableClock } from "../clock/preciseWallClock.js";
export { getEnvVar, getNumberEnvVar } from "../utils/getEnv.js";
export { OtelTaskLogger, logLevels } from "../logger/taskLogger.js";
export { ConsoleInterceptor } from "../consoleInterceptor.js";
export { TracingSDK, type TracingDiagnosticLogLevel, recordSpanException } from "../otel/index.js";
export {
TracingSDK,
type TracingDiagnosticLogLevel,
recordSpanException,
carrierFromContext,
} from "../otel/index.js";
export { StandardResourceCatalog } from "../resource-catalog/standardResourceCatalog.js";
export {
TaskContextSpanProcessor,
+58 -7
View File
@@ -2,8 +2,10 @@ import {
AsyncIterableStream,
createAsyncIterableStreamFromAsyncIterable,
SemanticInternalAttributes,
taskContext,
} from "@trigger.dev/core/v3";
import { logger } from "@trigger.dev/sdk/v3";
import { carrierFromContext } from "@trigger.dev/core/v3/otel";
import assert from "node:assert";
import fs from "node:fs";
import { Result, x, Options as XOptions } from "tinyexec";
@@ -17,6 +19,8 @@ export const python = {
async run(scriptArgs: string[] = [], options: PythonExecOptions = {}): Promise<Result> {
const pythonBin = process.env.PYTHON_BIN_PATH || "python";
const carrier = carrierFromContext();
return await logger.trace(
"python.run()",
async (span) => {
@@ -27,6 +31,12 @@ export const python = {
env: {
...process.env,
...options.env,
TRACEPARENT: carrier["traceparent"],
OTEL_RESOURCE_ATTRIBUTES: `${
SemanticInternalAttributes.EXECUTION_ENVIRONMENT
}=trigger,${Object.entries(taskContext.attributes)
.map(([key, value]) => `${key}=${value}`)
.join(",")}`,
},
},
throwOnError: false, // Ensure errors are handled manually
@@ -50,7 +60,7 @@ export const python = {
attributes: {
pythonBin,
args: scriptArgs.join(" "),
[SemanticInternalAttributes.STYLE_ICON]: "brand-python",
[SemanticInternalAttributes.STYLE_ICON]: "python",
},
}
);
@@ -69,6 +79,8 @@ export const python = {
async (span) => {
span.setAttribute("scriptPath", scriptPath);
const carrier = carrierFromContext();
const result = await x(
process.env.PYTHON_BIN_PATH || "python",
[scriptPath, ...scriptArgs],
@@ -79,6 +91,13 @@ export const python = {
env: {
...process.env,
...options.env,
TRACEPARENT: carrier["traceparent"],
OTEL_RESOURCE_ATTRIBUTES: `${
SemanticInternalAttributes.EXECUTION_ENVIRONMENT
}=trigger,${Object.entries(taskContext.attributes)
.map(([key, value]) => `${key}=${value}`)
.join(",")}`,
OTEL_LOG_LEVEL: "DEBUG",
},
},
throwOnError: false,
@@ -93,7 +112,7 @@ export const python = {
throw new Error(
`${scriptPath} ${scriptArgs.join(" ")} exited with a non-zero code ${
result.exitCode
}:\n${result.stderr}`
}:\n${result.stdout}\n${result.stderr}`
);
}
@@ -104,7 +123,7 @@ export const python = {
pythonBin: process.env.PYTHON_BIN_PATH || "python",
scriptPath,
args: scriptArgs.join(" "),
[SemanticInternalAttributes.STYLE_ICON]: "brand-python",
[SemanticInternalAttributes.STYLE_ICON]: "python",
},
}
);
@@ -124,6 +143,8 @@ export const python = {
async (tempFilePath) => {
span.setAttribute("tempFilePath", tempFilePath);
const carrier = carrierFromContext();
const pythonBin = process.env.PYTHON_BIN_PATH || "python";
const result = await x(pythonBin, [tempFilePath], {
...options,
@@ -132,6 +153,12 @@ export const python = {
env: {
...process.env,
...options.env,
TRACEPARENT: carrier["traceparent"],
OTEL_RESOURCE_ATTRIBUTES: `${
SemanticInternalAttributes.EXECUTION_ENVIRONMENT
}=trigger,${Object.entries(taskContext.attributes)
.map(([key, value]) => `${key}=${value}`)
.join(",")}`,
},
},
throwOnError: false,
@@ -157,7 +184,7 @@ export const python = {
pythonBin: process.env.PYTHON_BIN_PATH || "python",
contentPreview:
scriptContent.substring(0, 100) + (scriptContent.length > 100 ? "..." : ""),
[SemanticInternalAttributes.STYLE_ICON]: "brand-python",
[SemanticInternalAttributes.STYLE_ICON]: "python",
},
}
);
@@ -167,6 +194,8 @@ export const python = {
run(scriptArgs: string[] = [], options: PythonExecOptions = {}): AsyncIterableStream<string> {
const pythonBin = process.env.PYTHON_BIN_PATH || "python";
const carrier = carrierFromContext();
const pythonProcess = x(pythonBin, scriptArgs, {
...options,
nodeOptions: {
@@ -174,6 +203,12 @@ export const python = {
env: {
...process.env,
...options.env,
TRACEPARENT: carrier["traceparent"],
OTEL_RESOURCE_ATTRIBUTES: `${
SemanticInternalAttributes.EXECUTION_ENVIRONMENT
}=trigger,${Object.entries(taskContext.attributes)
.map(([key, value]) => `${key}=${value}`)
.join(",")}`,
},
},
throwOnError: false,
@@ -183,7 +218,7 @@ export const python = {
attributes: {
pythonBin,
args: scriptArgs.join(" "),
[SemanticInternalAttributes.STYLE_ICON]: "brand-python",
[SemanticInternalAttributes.STYLE_ICON]: "python",
},
});
@@ -206,6 +241,8 @@ export const python = {
const pythonBin = process.env.PYTHON_BIN_PATH || "python";
const carrier = carrierFromContext();
const pythonProcess = x(pythonBin, [scriptPath, ...scriptArgs], {
...options,
nodeOptions: {
@@ -213,6 +250,12 @@ export const python = {
env: {
...process.env,
...options.env,
TRACEPARENT: carrier["traceparent"],
OTEL_RESOURCE_ATTRIBUTES: `${
SemanticInternalAttributes.EXECUTION_ENVIRONMENT
}=trigger,${Object.entries(taskContext.attributes)
.map(([key, value]) => `${key}=${value}`)
.join(",")}`,
},
},
throwOnError: false,
@@ -223,7 +266,7 @@ export const python = {
pythonBin,
scriptPath,
args: scriptArgs.join(" "),
[SemanticInternalAttributes.STYLE_ICON]: "brand-python",
[SemanticInternalAttributes.STYLE_ICON]: "python",
},
});
@@ -243,6 +286,8 @@ export const python = {
const pythonScriptPath = createTempFileSync(`script_${Date.now()}.py`, scriptContent);
const carrier = carrierFromContext();
const pythonProcess = x(pythonBin, [pythonScriptPath], {
...options,
nodeOptions: {
@@ -250,6 +295,12 @@ export const python = {
env: {
...process.env,
...options.env,
TRACEPARENT: carrier["traceparent"],
OTEL_RESOURCE_ATTRIBUTES: `${
SemanticInternalAttributes.EXECUTION_ENVIRONMENT
}=trigger,${Object.entries(taskContext.attributes)
.map(([key, value]) => `${key}=${value}`)
.join(",")}`,
},
},
throwOnError: false,
@@ -260,7 +311,7 @@ export const python = {
pythonBin,
contentPreview:
scriptContent.substring(0, 100) + (scriptContent.length > 100 ? "..." : ""),
[SemanticInternalAttributes.STYLE_ICON]: "brand-python",
[SemanticInternalAttributes.STYLE_ICON]: "python",
},
});
+123 -112
View File
@@ -1718,11 +1718,27 @@ importers:
specifier: 3.23.8
version: 3.23.8
references/agent-loop:
references/bun-catalog:
dependencies:
'@trigger.dev/sdk':
specifier: workspace:*
version: link:../../packages/trigger-sdk
devDependencies:
'@types/bun':
specifier: ^1.1.6
version: 1.1.6
trigger.dev:
specifier: workspace:*
version: link:../../packages/cli-v3
references/d3-demo:
dependencies:
'@ai-sdk/openai':
specifier: 1.3.3
version: 1.3.3(zod@3.23.8)
'@trigger.dev/python':
specifier: workspace:*
version: link:../../packages/python
'@trigger.dev/react-hooks':
specifier: workspace:*
version: link:../../packages/react-hooks
@@ -1759,10 +1775,16 @@ importers:
zod:
specifier: 3.23.8
version: 3.23.8
zod-to-json-schema:
specifier: ^3.24.5
version: 3.24.5(zod@3.23.8)
devDependencies:
'@tailwindcss/postcss':
specifier: ^4
version: 4.0.17
'@trigger.dev/build':
specifier: workspace:*
version: link:../../packages/build
'@types/node':
specifier: ^20
version: 20.14.14
@@ -1782,19 +1804,6 @@ importers:
specifier: ^5
version: 5.5.4
references/bun-catalog:
dependencies:
'@trigger.dev/sdk':
specifier: workspace:*
version: link:../../packages/trigger-sdk
devDependencies:
'@types/bun':
specifier: ^1.1.6
version: 1.1.6
trigger.dev:
specifier: workspace:*
version: link:../../packages/cli-v3
references/hello-world:
dependencies:
'@trigger.dev/sdk':
@@ -2251,7 +2260,7 @@ packages:
dependencies:
'@ai-sdk/provider': 1.0.0
eventsource-parser: 3.0.0
nanoid: 3.3.7
nanoid: 3.3.11
secure-json-parse: 2.7.0
zod: 3.23.8
dev: true
@@ -2487,7 +2496,7 @@ packages:
json-schema: 0.4.0
secure-json-parse: 2.7.0
zod: 3.23.8
zod-to-json-schema: 3.23.5(zod@3.23.8)
zod-to-json-schema: 3.24.3(zod@3.23.8)
dev: true
/@ai-sdk/ui-utils@1.0.0(zod@3.23.8):
@@ -2502,7 +2511,7 @@ packages:
'@ai-sdk/provider': 1.0.0
'@ai-sdk/provider-utils': 2.0.0(zod@3.23.8)
zod: 3.23.8
zod-to-json-schema: 3.23.5(zod@3.23.8)
zod-to-json-schema: 3.24.3(zod@3.23.8)
dev: false
/@ai-sdk/ui-utils@1.0.1(zod@3.23.8):
@@ -2517,7 +2526,7 @@ packages:
'@ai-sdk/provider': 1.0.0
'@ai-sdk/provider-utils': 2.0.1(zod@3.23.8)
zod: 3.23.8
zod-to-json-schema: 3.23.5(zod@3.23.8)
zod-to-json-schema: 3.24.3(zod@3.23.8)
dev: true
/@ai-sdk/ui-utils@1.2.1(zod@3.23.8):
@@ -2529,7 +2538,7 @@ packages:
'@ai-sdk/provider': 1.1.0
'@ai-sdk/provider-utils': 2.2.1(zod@3.23.8)
zod: 3.23.8
zod-to-json-schema: 3.24.3(zod@3.23.8)
zod-to-json-schema: 3.24.5(zod@3.23.8)
dev: false
/@ai-sdk/vue@0.0.45(vue@3.4.38)(zod@3.23.8):
@@ -8413,20 +8422,20 @@ packages:
/@internationalized/date@3.5.5:
resolution: {integrity: sha512-H+CfYvOZ0LTJeeLOqm19E3uj/4YjrmOFtBufDHPfvtI80hFAMqtrp7oCACpe4Cil5l8S0Qu/9dYfZc/5lY8WQQ==}
dependencies:
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
dev: false
/@internationalized/message@3.1.1:
resolution: {integrity: sha512-ZgHxf5HAPIaR0th+w0RUD62yF6vxitjlprSxmLJ1tam7FOekqRSDELMg4Cr/DdszG5YLsp5BG3FgHgqquQZbqw==}
dependencies:
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
intl-messageformat: 10.5.8
dev: false
/@internationalized/message@3.1.4:
resolution: {integrity: sha512-Dygi9hH1s7V9nha07pggCkvmRfDd3q2lWnMGvrJyrOwYMe1yj4D2T9BoH9I6MGR7xz0biQrtLPsqUkqXzIrBOw==}
dependencies:
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
intl-messageformat: 10.5.8
dev: false
@@ -8439,7 +8448,7 @@ packages:
/@internationalized/number@3.5.3:
resolution: {integrity: sha512-rd1wA3ebzlp0Mehj5YTuTI50AQEx80gWFyHcQu+u91/5NgdwBecO8BH6ipPfE+lmQ9d63vpB3H9SHoIUiupllw==}
dependencies:
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
dev: false
/@internationalized/string@3.2.0:
@@ -8451,7 +8460,7 @@ packages:
/@internationalized/string@3.2.3:
resolution: {integrity: sha512-9kpfLoA8HegiWTeCbR2livhdVeKobCnVv8tlJ6M2jF+4tcMqDo94ezwlnrUANBWPgd8U7OXIHCk2Ov2qhk4KXw==}
dependencies:
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
dev: false
/@ioredis/commands@1.2.0:
@@ -12541,7 +12550,7 @@ packages:
'@react-aria/utils': 3.25.2(react@18.2.0)
'@react-types/breadcrumbs': 3.7.7(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -12585,7 +12594,7 @@ packages:
'@react-stately/toggle': 3.7.7(react@18.2.0)
'@react-types/button': 3.9.6(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -12604,7 +12613,7 @@ packages:
'@react-types/button': 3.9.6(react@18.2.0)
'@react-types/calendar': 3.4.9(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -12662,7 +12671,7 @@ packages:
'@react-stately/toggle': 3.7.7(react@18.2.0)
'@react-types/checkbox': 3.8.3(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -12686,7 +12695,7 @@ packages:
'@react-types/button': 3.9.6(react@18.2.0)
'@react-types/combobox': 3.12.1(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -12739,7 +12748,7 @@ packages:
'@react-types/datepicker': 3.8.2(react@18.2.0)
'@react-types/dialog': 3.5.12(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -12799,7 +12808,7 @@ packages:
'@react-aria/utils': 3.25.2(react@18.2.0)
'@react-types/dialog': 3.5.12(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -12839,7 +12848,7 @@ packages:
'@react-stately/dnd': 3.4.2(react@18.2.0)
'@react-types/button': 3.9.6(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -12865,7 +12874,7 @@ packages:
'@react-aria/interactions': 3.22.2(react@18.2.0)
'@react-aria/utils': 3.25.2(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
clsx: 2.1.1
react: 18.2.0
dev: false
@@ -12892,7 +12901,7 @@ packages:
'@react-aria/utils': 3.25.2(react@18.2.0)
'@react-stately/form': 3.0.5(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -12914,7 +12923,7 @@ packages:
'@react-types/checkbox': 3.8.3(react@18.2.0)
'@react-types/grid': 3.2.8(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -12938,7 +12947,7 @@ packages:
'@react-types/checkbox': 3.6.0(react@18.2.0)
'@react-types/grid': 3.2.3(react@18.2.0)
'@react-types/shared': 3.22.0(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -12978,7 +12987,7 @@ packages:
'@react-stately/list': 3.10.8(react@18.2.0)
'@react-stately/tree': 3.8.4(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -13011,7 +13020,7 @@ packages:
'@react-aria/ssr': 3.9.5(react@18.2.0)
'@react-aria/utils': 3.25.2(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13035,7 +13044,7 @@ packages:
'@react-aria/ssr': 3.9.5(react@18.2.0)
'@react-aria/utils': 3.25.2(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13046,7 +13055,7 @@ packages:
dependencies:
'@react-aria/utils': 3.25.2(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13085,7 +13094,7 @@ packages:
'@react-aria/utils': 3.25.2(react@18.2.0)
'@react-types/link': 3.5.7(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13122,7 +13131,7 @@ packages:
'@react-stately/list': 3.10.8(react@18.2.0)
'@react-types/listbox': 3.5.1(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -13130,13 +13139,13 @@ packages:
/@react-aria/live-announcer@3.3.1:
resolution: {integrity: sha512-hsc77U7S16trM86d+peqJCOCQ7/smO1cybgdpOuzXyiwcHQw8RQ4GrXrS37P4Ux/44E9nMZkOwATQRT2aK8+Ew==}
dependencies:
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
dev: false
/@react-aria/live-announcer@3.3.4:
resolution: {integrity: sha512-w8lxs35QrRrn6pBNzVfyGOeqWdxeVKf9U6bXIVwhq7rrTqRULL8jqy8RJIMfIs1s8G5FpwWYjyBOjl2g5Cu1iA==}
dependencies:
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
dev: false
/@react-aria/menu@3.12.0(react-dom@18.2.0)(react@18.2.0):
@@ -13180,7 +13189,7 @@ packages:
'@react-types/button': 3.9.6(react@18.2.0)
'@react-types/menu': 3.9.11(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -13193,7 +13202,7 @@ packages:
'@react-aria/progress': 3.4.16(react@18.2.0)
'@react-types/meter': 3.4.3(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13246,7 +13255,7 @@ packages:
'@react-types/button': 3.9.6(react@18.2.0)
'@react-types/numberfield': 3.8.5(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -13288,7 +13297,7 @@ packages:
'@react-types/button': 3.9.6(react@18.2.0)
'@react-types/overlays': 3.8.9(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -13303,7 +13312,7 @@ packages:
'@react-aria/utils': 3.25.2(react@18.2.0)
'@react-types/progress': 3.5.6(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13353,7 +13362,7 @@ packages:
'@react-stately/radio': 3.10.7(react@18.2.0)
'@react-types/radio': 3.8.3(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13385,7 +13394,7 @@ packages:
'@react-types/button': 3.9.6(react@18.2.0)
'@react-types/searchfield': 3.5.8(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13432,7 +13441,7 @@ packages:
'@react-types/button': 3.9.6(react@18.2.0)
'@react-types/select': 3.9.6(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -13466,7 +13475,7 @@ packages:
'@react-aria/utils': 3.25.2(react@18.2.0)
'@react-stately/selection': 3.16.2(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -13489,7 +13498,7 @@ packages:
dependencies:
'@react-aria/utils': 3.25.2(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13506,7 +13515,7 @@ packages:
'@react-stately/slider': 3.5.7(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@react-types/slider': 3.7.5(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13554,7 +13563,7 @@ packages:
'@react-aria/utils': 3.25.2(react@18.2.0)
'@react-types/button': 3.9.6(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -13575,7 +13584,7 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
dependencies:
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13600,7 +13609,7 @@ packages:
'@react-stately/toggle': 3.7.7(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@react-types/switch': 3.5.5(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13650,7 +13659,7 @@ packages:
'@react-types/grid': 3.2.8(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@react-types/table': 3.10.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -13686,7 +13695,7 @@ packages:
'@react-stately/tabs': 3.6.9(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@react-types/tabs': 3.3.9(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -13726,7 +13735,7 @@ packages:
'@react-stately/list': 3.10.8(react@18.2.0)
'@react-types/button': 3.9.6(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -13761,7 +13770,7 @@ packages:
'@react-stately/utils': 3.10.3(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@react-types/textfield': 3.9.6(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13775,7 +13784,7 @@ packages:
'@react-aria/utils': 3.23.0(react@18.2.0)
'@react-stately/toggle': 3.7.0(react@18.2.0)
'@react-types/checkbox': 3.6.0(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13790,7 +13799,7 @@ packages:
'@react-stately/toggle': 3.7.7(react@18.2.0)
'@react-types/checkbox': 3.8.3(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13820,7 +13829,7 @@ packages:
'@react-stately/tooltip': 3.4.12(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@react-types/tooltip': 3.4.11(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -13845,7 +13854,7 @@ packages:
'@react-aria/ssr': 3.9.5(react@18.2.0)
'@react-stately/utils': 3.10.3(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
clsx: 2.1.1
react: 18.2.0
dev: false
@@ -13858,7 +13867,7 @@ packages:
'@react-aria/interactions': 3.22.2(react@18.2.0)
'@react-aria/utils': 3.25.2(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14546,7 +14555,7 @@ packages:
'@react-stately/utils': 3.10.3(react@18.2.0)
'@react-types/calendar': 3.4.9(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14572,7 +14581,7 @@ packages:
'@react-stately/utils': 3.10.3(react@18.2.0)
'@react-types/checkbox': 3.8.3(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14592,7 +14601,7 @@ packages:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
dependencies:
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14626,7 +14635,7 @@ packages:
'@react-stately/utils': 3.10.3(react@18.2.0)
'@react-types/combobox': 3.12.1(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14652,7 +14661,7 @@ packages:
'@react-stately/utils': 3.10.3(react@18.2.0)
'@react-types/datepicker': 3.8.2(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14690,7 +14699,7 @@ packages:
dependencies:
'@react-stately/selection': 3.16.2(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14703,7 +14712,7 @@ packages:
/@react-stately/flags@3.0.3:
resolution: {integrity: sha512-/ha7XFA0RZTQsbzSPwu3KkbNMgbvuM0GuMTYLTBWpgBrovBNTM+QqI/PfZTdHg8PwCYF4H5Y8gjdSpdulCvJFw==}
dependencies:
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
dev: false
/@react-stately/form@3.0.0(react@18.2.0):
@@ -14722,7 +14731,7 @@ packages:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
dependencies:
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14735,7 +14744,7 @@ packages:
'@react-stately/selection': 3.14.2(react@18.2.0)
'@react-types/grid': 3.2.3(react@18.2.0)
'@react-types/shared': 3.22.0(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14748,7 +14757,7 @@ packages:
'@react-stately/selection': 3.16.2(react@18.2.0)
'@react-types/grid': 3.2.8(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14774,7 +14783,7 @@ packages:
'@react-stately/selection': 3.16.2(react@18.2.0)
'@react-stately/utils': 3.10.3(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14798,7 +14807,7 @@ packages:
'@react-stately/overlays': 3.6.10(react@18.2.0)
'@react-types/menu': 3.9.11(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14824,7 +14833,7 @@ packages:
'@react-stately/form': 3.0.5(react@18.2.0)
'@react-stately/utils': 3.10.3(react@18.2.0)
'@react-types/numberfield': 3.8.5(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14835,7 +14844,7 @@ packages:
dependencies:
'@react-stately/utils': 3.10.3(react@18.2.0)
'@react-types/overlays': 3.8.9(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14872,7 +14881,7 @@ packages:
'@react-stately/utils': 3.10.3(react@18.2.0)
'@react-types/radio': 3.8.3(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14894,7 +14903,7 @@ packages:
dependencies:
'@react-stately/utils': 3.10.3(react@18.2.0)
'@react-types/searchfield': 3.5.8(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14922,7 +14931,7 @@ packages:
'@react-stately/overlays': 3.6.10(react@18.2.0)
'@react-types/select': 3.9.6(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14946,7 +14955,7 @@ packages:
'@react-stately/collections': 3.10.9(react@18.2.0)
'@react-stately/utils': 3.10.3(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -14970,7 +14979,7 @@ packages:
'@react-stately/utils': 3.10.3(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@react-types/slider': 3.7.5(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -15004,7 +15013,7 @@ packages:
'@react-types/grid': 3.2.8(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@react-types/table': 3.10.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -15028,7 +15037,7 @@ packages:
'@react-stately/list': 3.10.8(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@react-types/tabs': 3.3.9(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -15050,7 +15059,7 @@ packages:
dependencies:
'@react-stately/utils': 3.10.3(react@18.2.0)
'@react-types/checkbox': 3.8.3(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -15061,7 +15070,7 @@ packages:
dependencies:
'@react-stately/overlays': 3.6.10(react@18.2.0)
'@react-types/tooltip': 3.4.11(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -15098,7 +15107,7 @@ packages:
'@react-stately/selection': 3.16.2(react@18.2.0)
'@react-stately/utils': 3.10.3(react@18.2.0)
'@react-types/shared': 3.24.1(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -15107,7 +15116,7 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0
dependencies:
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -15127,7 +15136,7 @@ packages:
dependencies:
'@react-aria/utils': 3.23.0(react@18.2.0)
'@react-types/shared': 3.22.0(react@18.2.0)
'@swc/helpers': 0.5.5
'@swc/helpers': 0.5.15
react: 18.2.0
dev: false
@@ -17379,7 +17388,7 @@ packages:
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
dependencies:
'@swc/counter': 0.1.3
tslib: 2.6.2
tslib: 2.8.1
dev: false
/@swc/types@0.1.6:
@@ -19442,7 +19451,7 @@ packages:
secure-json-parse: 2.7.0
svelte: 4.2.19
zod: 3.23.8
zod-to-json-schema: 3.23.5(zod@3.23.8)
zod-to-json-schema: 3.24.3(zod@3.23.8)
transitivePeerDependencies:
- solid-js
- vue
@@ -19468,7 +19477,7 @@ packages:
jsondiffpatch: 0.6.0
react: 18.3.1
zod: 3.23.8
zod-to-json-schema: 3.23.5(zod@3.23.8)
zod-to-json-schema: 3.24.3(zod@3.23.8)
dev: false
/ai@4.0.2(react@18.3.1)(zod@3.23.8):
@@ -19491,7 +19500,7 @@ packages:
jsondiffpatch: 0.6.0
react: 18.3.1
zod: 3.23.8
zod-to-json-schema: 3.23.5(zod@3.23.8)
zod-to-json-schema: 3.24.3(zod@3.23.8)
dev: true
/ai@4.2.5(react@19.0.0)(zod@3.23.8):
@@ -21007,6 +21016,7 @@ packages:
/color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
requiresBuild: true
/color-string@1.9.1:
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
@@ -22265,6 +22275,7 @@ packages:
dependencies:
graceful-fs: 4.2.11
tapable: 2.2.1
dev: true
/enhanced-resolve@5.18.1:
resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
@@ -22272,7 +22283,6 @@ packages:
dependencies:
graceful-fs: 4.2.11
tapable: 2.2.1
dev: true
/enquirer@2.3.6:
resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==}
@@ -23926,7 +23936,7 @@ packages:
resolution: {integrity: sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==}
engines: {node: '>= 12'}
dependencies:
tslib: 2.6.2
tslib: 2.8.1
dev: false
/fill-range@7.0.1:
@@ -24167,7 +24177,7 @@ packages:
dependencies:
react: 18.3.1
react-dom: 18.2.0(react@18.3.1)
tslib: 2.6.2
tslib: 2.8.1
optionalDependencies:
'@emotion/is-prop-valid': 0.8.8
dev: false
@@ -24283,7 +24293,7 @@ packages:
resolution: {integrity: sha512-igqQZTtS4GFwkDWA5gFWQye9Lmkx184Y17+x9flFq8HC68RVuuQGPeQtBFdMMlnac7/2Bq1n+1rkp4S8ZAu7kA==}
dependencies:
adm-zip: 0.5.16
tslib: 2.6.2
tslib: 2.8.1
dev: false
/generic-names@4.0.0:
@@ -25242,6 +25252,7 @@ packages:
/is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
requiresBuild: true
dev: false
/is-bigint@1.0.4:
@@ -27487,7 +27498,6 @@ packages:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: false
/nanoid@3.3.4:
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
@@ -29235,7 +29245,7 @@ packages:
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.7
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.0
dev: false
@@ -29252,7 +29262,7 @@ packages:
resolution: {integrity: sha512-Aweb9unOEpQ3ezu4Q00DPvvM2ZTUitJdNKeP/+uQgr1IBIqu574IaZoURId7BKtWMREwzKa9OgzPzezWGPWFQw==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.7
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.0
@@ -31513,6 +31523,7 @@ packages:
/simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
requiresBuild: true
dependencies:
is-arrayish: 0.3.2
dev: false
@@ -32399,7 +32410,7 @@ packages:
engines: {node: ^14.18.0 || >=16.0.0}
dependencies:
'@pkgr/utils': 2.3.1
tslib: 2.6.2
tslib: 2.8.1
dev: true
/tailwind-merge@1.12.0:
@@ -34823,7 +34834,7 @@ packages:
acorn-import-assertions: 1.9.0(acorn@8.12.1)
browserslist: 4.23.3
chrome-trace-event: 1.0.3
enhanced-resolve: 5.15.0
enhanced-resolve: 5.18.1
es-module-lexer: 1.3.1
eslint-scope: 5.1.1
events: 3.3.0
@@ -34863,7 +34874,7 @@ packages:
acorn-import-assertions: 1.9.0(acorn@8.12.1)
browserslist: 4.23.3
chrome-trace-event: 1.0.3
enhanced-resolve: 5.15.0
enhanced-resolve: 5.18.1
es-module-lexer: 1.3.1
eslint-scope: 5.1.1
events: 3.3.0
@@ -35328,15 +35339,15 @@ packages:
zod: 3.23.8
dev: false
/zod-to-json-schema@3.23.5(zod@3.23.8):
resolution: {integrity: sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==}
/zod-to-json-schema@3.24.3(zod@3.23.8):
resolution: {integrity: sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A==}
peerDependencies:
zod: ^3.23.3
zod: ^3.24.1
dependencies:
zod: 3.23.8
/zod-to-json-schema@3.24.3(zod@3.23.8):
resolution: {integrity: sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A==}
/zod-to-json-schema@3.24.5(zod@3.23.8):
resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==}
peerDependencies:
zod: ^3.24.1
dependencies:
@@ -1,10 +0,0 @@
import { z } from "zod";
export const AgentLoopMetadata = z.object({
waitToken: z.object({
id: z.string(),
publicAccessToken: z.string(),
}),
});
export type AgentLoopMetadata = z.infer<typeof AgentLoopMetadata>;
-7
View File
@@ -1,7 +0,0 @@
import { defineConfig } from "@trigger.dev/sdk";
export default defineConfig({
project: "proj_hgknlxlflfcemqogujnt",
dirs: ["./src/trigger"],
maxDuration: 3600,
});
@@ -1,5 +1,5 @@
{
"name": "references-agent-loop",
"name": "references-d3-demo",
"version": "0.1.0",
"private": true,
"scripts": {
@@ -8,10 +8,18 @@
"start": "next start",
"lint": "next lint",
"dev:trigger": "trigger dev",
"deploy": "trigger deploy"
"deploy": "pnpm run python:compile-requirements && trigger deploy",
"python:install-requirements": "uv pip sync requirements.txt",
"python:compile-requirements": "uv pip compile requirements.in -o requirements.txt",
"python:install": "pnpm run python:compile-requirements && pnpm run python:install-requirements",
"python:create-env": "uv venv .venv"
},
"dependencies": {
"@ai-sdk/openai": "1.3.3",
"@trigger.dev/react-hooks": "workspace:*",
"@trigger.dev/sdk": "workspace:*",
"@trigger.dev/python": "workspace:*",
"ai": "4.2.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.484.0",
@@ -20,10 +28,8 @@
"react-dom": "^19.0.0",
"tailwind-merge": "^3.0.2",
"tw-animate-css": "^1.2.4",
"@trigger.dev/react-hooks": "workspace:*",
"@trigger.dev/sdk": "workspace:*",
"ai": "4.2.5",
"zod": "3.23.8"
"zod": "3.23.8",
"zod-to-json-schema": "^3.24.5"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
@@ -31,7 +37,8 @@
"@types/react": "^19",
"@types/react-dom": "^19",
"tailwindcss": "^4.0.17",
"typescript": "^5",
"trigger.dev": "workspace:*"
"trigger.dev": "workspace:*",
"@trigger.dev/build": "workspace:*",
"typescript": "^5"
}
}

Before

Width:  |  Height:  |  Size: 391 B

After

Width:  |  Height:  |  Size: 391 B

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 128 B

After

Width:  |  Height:  |  Size: 128 B

Before

Width:  |  Height:  |  Size: 385 B

After

Width:  |  Height:  |  Size: 385 B

+5
View File
@@ -0,0 +1,5 @@
crawl4ai
playwright
openai-agents
urllib3<2.0.0
logfire
+309
View File
@@ -0,0 +1,309 @@
# This file was autogenerated by uv via the following command:
# uv pip compile requirements.in -o requirements.txt
aiofiles==24.1.0
# via crawl4ai
aiohappyeyeballs==2.6.1
# via aiohttp
aiohttp==3.11.14
# via
# crawl4ai
# litellm
aiosignal==1.3.2
# via aiohttp
aiosqlite==0.21.0
# via crawl4ai
annotated-types==0.7.0
# via pydantic
anyio==4.9.0
# via
# httpx
# mcp
# openai
# sse-starlette
# starlette
attrs==25.3.0
# via
# aiohttp
# jsonschema
# referencing
beautifulsoup4==4.13.3
# via crawl4ai
certifi==2025.1.31
# via
# httpcore
# httpx
# requests
cffi==1.17.1
# via cryptography
charset-normalizer==3.4.1
# via requests
click==8.1.8
# via
# crawl4ai
# litellm
# nltk
# uvicorn
colorama==0.4.6
# via
# crawl4ai
# griffe
crawl4ai==0.5.0.post8
# via -r requirements.in
cryptography==44.0.2
# via pyopenssl
cssselect==1.3.0
# via crawl4ai
deprecated==1.2.18
# via
# opentelemetry-api
# opentelemetry-exporter-otlp-proto-http
# opentelemetry-semantic-conventions
distro==1.9.0
# via openai
executing==2.2.0
# via logfire
fake-http-header==0.3.5
# via tf-playwright-stealth
fake-useragent==2.1.0
# via crawl4ai
faust-cchardet==2.1.19
# via crawl4ai
filelock==3.18.0
# via huggingface-hub
frozenlist==1.5.0
# via
# aiohttp
# aiosignal
fsspec==2025.3.0
# via huggingface-hub
googleapis-common-protos==1.69.2
# via opentelemetry-exporter-otlp-proto-http
greenlet==3.1.1
# via playwright
griffe==1.6.3
# via openai-agents
h11==0.14.0
# via
# httpcore
# uvicorn
httpcore==1.0.7
# via httpx
httpx==0.28.1
# via
# crawl4ai
# litellm
# mcp
# openai
httpx-sse==0.4.0
# via mcp
huggingface-hub==0.29.3
# via tokenizers
humanize==4.12.2
# via crawl4ai
idna==3.10
# via
# anyio
# httpx
# requests
# yarl
importlib-metadata==8.6.1
# via
# litellm
# opentelemetry-api
jinja2==3.1.6
# via litellm
jiter==0.9.0
# via openai
joblib==1.4.2
# via nltk
jsonschema==4.23.0
# via litellm
jsonschema-specifications==2024.10.1
# via jsonschema
litellm==1.64.1
# via crawl4ai
logfire==3.11.0
# via -r requirements.in
lxml==5.3.1
# via crawl4ai
markdown-it-py==3.0.0
# via rich
markupsafe==3.0.2
# via jinja2
mcp==1.5.0
# via openai-agents
mdurl==0.1.2
# via markdown-it-py
multidict==6.2.0
# via
# aiohttp
# yarl
nltk==3.9.1
# via crawl4ai
numpy==2.2.4
# via
# crawl4ai
# rank-bm25
openai==1.68.2
# via
# litellm
# openai-agents
openai-agents==0.0.7
# via -r requirements.in
opentelemetry-api==1.31.1
# via
# opentelemetry-exporter-otlp-proto-http
# opentelemetry-instrumentation
# opentelemetry-sdk
# opentelemetry-semantic-conventions
opentelemetry-exporter-otlp-proto-common==1.31.1
# via opentelemetry-exporter-otlp-proto-http
opentelemetry-exporter-otlp-proto-http==1.31.1
# via logfire
opentelemetry-instrumentation==0.52b1
# via logfire
opentelemetry-proto==1.31.1
# via
# opentelemetry-exporter-otlp-proto-common
# opentelemetry-exporter-otlp-proto-http
opentelemetry-sdk==1.31.1
# via
# logfire
# opentelemetry-exporter-otlp-proto-http
opentelemetry-semantic-conventions==0.52b1
# via
# opentelemetry-instrumentation
# opentelemetry-sdk
packaging==24.2
# via
# huggingface-hub
# opentelemetry-instrumentation
pillow==10.4.0
# via crawl4ai
playwright==1.51.0
# via
# -r requirements.in
# crawl4ai
# tf-playwright-stealth
propcache==0.3.1
# via
# aiohttp
# yarl
protobuf==5.29.4
# via
# googleapis-common-protos
# logfire
# opentelemetry-proto
psutil==7.0.0
# via crawl4ai
pycparser==2.22
# via cffi
pydantic==2.10.6
# via
# crawl4ai
# litellm
# mcp
# openai
# openai-agents
# pydantic-settings
pydantic-core==2.27.2
# via pydantic
pydantic-settings==2.8.1
# via mcp
pyee==12.1.1
# via playwright
pygments==2.19.1
# via rich
pyopenssl==25.0.0
# via crawl4ai
pyperclip==1.9.0
# via crawl4ai
python-dotenv==1.1.0
# via
# crawl4ai
# litellm
# pydantic-settings
pyyaml==6.0.2
# via huggingface-hub
rank-bm25==0.2.2
# via crawl4ai
referencing==0.36.2
# via
# jsonschema
# jsonschema-specifications
regex==2024.11.6
# via
# nltk
# tiktoken
requests==2.32.3
# via
# crawl4ai
# huggingface-hub
# openai-agents
# opentelemetry-exporter-otlp-proto-http
# tiktoken
rich==13.9.4
# via
# crawl4ai
# logfire
rpds-py==0.24.0
# via
# jsonschema
# referencing
sniffio==1.3.1
# via
# anyio
# openai
snowballstemmer==2.2.0
# via crawl4ai
soupsieve==2.6
# via beautifulsoup4
sse-starlette==2.2.1
# via mcp
starlette==0.46.1
# via
# mcp
# sse-starlette
tf-playwright-stealth==1.1.2
# via crawl4ai
tiktoken==0.9.0
# via litellm
tokenizers==0.21.1
# via litellm
tqdm==4.67.1
# via
# huggingface-hub
# nltk
# openai
types-requests==2.31.0.6
# via openai-agents
types-urllib3==1.26.25.14
# via types-requests
typing-extensions==4.13.0
# via
# aiosqlite
# beautifulsoup4
# huggingface-hub
# logfire
# openai
# openai-agents
# opentelemetry-sdk
# pydantic
# pydantic-core
# pyee
urllib3==1.26.20
# via
# -r requirements.in
# requests
uvicorn==0.34.0
# via mcp
wrapt==1.17.2
# via
# deprecated
# opentelemetry-instrumentation
xxhash==3.5.0
# via crawl4ai
yarl==1.18.3
# via aiohttp
zipp==3.21.0
# via importlib-metadata

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

@@ -2,7 +2,7 @@ import MainApp from "@/components/main-app";
import { auth } from "@trigger.dev/sdk";
export default async function Home() {
const publicAccessToken = await auth.createTriggerPublicToken("agent-loop-example");
const publicAccessToken = await auth.createTriggerPublicToken("chat-example");
return <MainApp publicAccessToken={publicAccessToken} />;
}
@@ -7,7 +7,7 @@ import { Send } from "lucide-react";
import ChatInterface from "@/components/chat-interface";
import InitialPrompt from "@/components/initial-prompt";
import { useRealtimeTaskTriggerWithStreams, useWaitToken } from "@trigger.dev/react-hooks";
import type { agentLoopExample } from "@/trigger/agent";
import type { chatExample } from "@/trigger/chat";
import { AgentLoopMetadata } from "@/trigger/schemas";
import type { TextStreamPart } from "ai";
@@ -17,14 +17,14 @@ type ResponseStreams = {
[K in `responses.${number | string}`]: TextStreamPart<{}>;
};
export function useAgentLoop({ publicAccessToken }: { publicAccessToken: string }) {
const triggerInstance = useRealtimeTaskTriggerWithStreams<
typeof agentLoopExample,
ResponseStreams
>("agent-loop-example", {
accessToken: publicAccessToken,
baseURL: process.env.NEXT_PUBLIC_TRIGGER_API_URL,
});
export function useChat({ publicAccessToken }: { publicAccessToken: string }) {
const triggerInstance = useRealtimeTaskTriggerWithStreams<typeof chatExample, ResponseStreams>(
"chat-example",
{
accessToken: publicAccessToken,
baseURL: process.env.NEXT_PUBLIC_TRIGGER_API_URL,
}
);
const [conversation, setConversation] = useState<ChatConversation>([]);
const [isLoading, setIsLoading] = useState(false);
const [waitToken, setWaitToken] = useState<AgentLoopMetadata | null>(null);
@@ -86,7 +86,7 @@ export function useAgentLoop({ publicAccessToken }: { publicAccessToken: string
}
export default function MainApp({ publicAccessToken }: { publicAccessToken: string }) {
const { continueConversation, conversation, isLoading } = useAgentLoop({ publicAccessToken });
const { continueConversation, conversation, isLoading } = useChat({ publicAccessToken });
const [input, setInput] = useState("");
const handleSubmit = (e: React.FormEvent) => {
@@ -0,0 +1,36 @@
import type { BuildContext, BuildExtension } from "@trigger.dev/build";
// This is a custom build extension to install Playwright and Chromium
export function installPlaywrightChromium(): BuildExtension {
return {
name: "InstallPlaywrightChromium",
onBuildComplete(context: BuildContext) {
const instructions = [
// Base and Chromium dependencies
`RUN apt-get update && apt-get install -y --no-install-recommends \
curl unzip npm libnspr4 libatk1.0-0 libatk-bridge2.0-0 libatspi2.0-0 \
libasound2 libnss3 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \
libgbm1 libxkbcommon0 \
&& apt-get clean && rm -rf /var/lib/apt/lists/*`,
// Install Playwright and Chromium
`RUN npm install -g playwright`,
`RUN mkdir -p /ms-playwright`,
`RUN PLAYWRIGHT_BROWSERS_PATH=/ms-playwright python -m playwright install --with-deps chromium`,
];
context.addLayer({
id: "playwright",
image: { instructions },
deploy: {
env: {
PLAYWRIGHT_BROWSERS_PATH: "/ms-playwright",
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1",
PLAYWRIGHT_SKIP_BROWSER_VALIDATION: "1",
},
override: true,
},
});
},
};
}
@@ -5,9 +5,9 @@ import { z } from "zod";
const MAX_STEPS = 10;
export const agentLoopExample = schemaTask({
id: "agent-loop-example",
description: "Agent loop example",
export const chatExample = schemaTask({
id: "chat-example",
description: "Chat example",
schema: z.object({
model: z.string().default("chatgpt-4o-latest"),
prompt: z.string().default("Hello, how are you?"),
+197
View File
@@ -0,0 +1,197 @@
import { batch, logger, schemaTask, wait } from "@trigger.dev/sdk/v3";
import { createHash } from "node:crypto";
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
import { CSVRowPayload, RowEnrichmentResult } from "./schemas";
import { python } from "@trigger.dev/python";
export const d3Demo = schemaTask({
id: "d3-demo",
description: "D3 Demo - Enriches a CSV dataset using Trigger.dev and the python OpenAI Agent SDK",
schema: z.object({
url: z.string().url().describe("The URL of the CSV dataset to enrich"),
}),
run: async ({ url }) => {
const response = await fetch(url);
const csv = await response.text();
const header = csv.split("\n")[0];
const rows = csv.split("\n").slice(1);
// Each payload has a header, url, and a single row
const payloads = rows.map((row) => ({
header,
url,
row,
}));
const chunkedPayloads = [];
for (let i = 0; i < payloads.length; i += 500) {
chunkedPayloads.push(payloads.slice(i, i + 500));
}
// Process each chunk
for (const chunk of chunkedPayloads) {
const results = await batch.triggerAndWait<typeof handleCSVRow>(
chunk.map((payload) => ({
id: "handle-csv-row",
payload,
options: {
idempotencyKey: createIdempotencyKey(payload),
},
}))
);
for (const result of results.runs) {
if (result.ok) {
// Pretty print the result of the enrichment
logger.log(`Enriched row ${result.output.row}`, result.output);
} else {
logger.error(`Error enriching row: ${result.error}`);
}
}
}
},
});
const ROW_AGENT_RUNNER_COUNT = 10;
export const handleCSVRow = schemaTask({
id: "handle-csv-row",
description: "Handles a single row of a CSV dataset",
schema: CSVRowPayload,
run: async ({ header, row, url }) => {
// Batch trigger the rowAgentCoordinator to try 10 different enrichments on the same row
const results = await batch.triggerAndWait<typeof rowAgentCoordinator>(
Array.from({ length: ROW_AGENT_RUNNER_COUNT }, () => ({
id: "row-agent-coordinator" as const,
payload: { row: { header, row, url } },
}))
);
const outputs = results.runs.filter((result) => result.ok).map((result) => result.output);
const enrichment = await enrichmentResultsEvaluator
.triggerAndWait({
results: outputs,
})
.unwrap();
return {
enrichment,
header,
row,
url,
};
},
});
export const rowAgentCoordinator = schemaTask({
id: "row-agent-coordinator",
description: "Coordinators the row agent",
schema: z.object({
row: CSVRowPayload,
}),
run: async ({ row }) => {
const waitToken = await wait.createToken({
timeout: "2m",
});
const jsonSchema = zodToJsonSchema(RowEnrichmentResult);
await rowAgentRunner.trigger({
row,
waitToken,
jsonSchema,
});
const result = await wait.forToken<RowEnrichmentResult>(waitToken.id);
if (!result.ok) {
throw result.error;
}
return result.output;
},
});
export const rowAgentRunner = schemaTask({
id: "row-agent-runner",
description: "Runs the row agent",
schema: z.object({
row: CSVRowPayload,
waitToken: z.object({
id: z
.string()
.describe(
"The wait token that the OpenAI Agent SDK will use to signal back to the coordinator"
),
publicAccessToken: z
.string()
.describe(
"The public access token that the OpenAI Agent SDK will use to signal back to the coordinator"
),
}),
jsonSchema: z.any().describe("The JSON schema of the result"),
disableWaitTokenCompletion: z
.boolean()
.default(false)
.describe("Whether to disable wait token completion"),
}),
run: async ({ row, waitToken, jsonSchema, disableWaitTokenCompletion }) => {
const inputData = JSON.stringify({
row,
waitToken,
jsonSchema,
disableWaitTokenCompletion,
});
logger.info("process.env", {
env: process.env,
});
const result = await python.runScript("./src/trigger/python/agent.py", [inputData]);
logger.debug("row-agent-runner", {
result,
});
return {} as unknown as RowEnrichmentResult;
},
catchError: async ({ error }) => {
logger.error("row-agent-runner", {
error,
});
},
});
export const enrichmentResultsEvaluator = schemaTask({
id: "enrichment-results-evaluator",
description: "Evaluates the enrichment results",
schema: z.object({
results: z.array(RowEnrichmentResult),
}),
run: async ({ results }) => {
// Combine the results into a single object (this is a placeholder for a more complex evaluation)
const combinedResult = results.reduce(
(acc, result) => ({
...acc,
...result,
}),
{} as RowEnrichmentResult
);
return combinedResult;
},
});
// Create a hash of the payload using Node.js crypto
// Ideally, you'd do a stable serialization of the payload before hashing, to ensure the same payload always results in the same hash
function createIdempotencyKey(payload: CSVRowPayload): string {
const content = `${payload.header}\n${payload.row}`;
const hash = createHash("sha256");
hash.update(content);
return hash.digest("hex");
}
@@ -0,0 +1,171 @@
import asyncio
import sys
import os
import json
import requests
from typing import Optional
from pydantic import BaseModel, Field
from agents import Agent, Runner, WebSearchTool, trace
import logfire
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
from logfire.propagate import attach_context
logfire.configure(
service_name='d3-demo',
send_to_logfire="if-token-present",
distributed_tracing=True
)
logfire.instrument_openai_agents()
class CSVRowPayload(BaseModel):
header: str = Field(description="The header of the CSV dataset")
row: str = Field(description="The row to handle")
url: str = Field(description="The URL of the CSV dataset")
class WaitToken(BaseModel):
id: str = Field(description="The wait token ID")
publicAccessToken: str = Field(description="The public access token for authorization")
class AgentInput(BaseModel):
row: CSVRowPayload
waitToken: WaitToken
jsonSchema: dict
disableWaitTokenCompletion: bool = Field(default=False, description="Whether to disable wait token completion")
class BasicInfo(BaseModel):
name: str = Field(description="The name of the row")
email: str = Field(description="The email of the row")
firstName: Optional[str] = Field(None, description="The first name of the row")
lastName: Optional[str] = Field(None, description="The last name of the row")
preferredName: Optional[str] = Field(None, description="The preferred name of the row")
class CompanyInfo(BaseModel):
name: str = Field(description="The name of the company")
industry: str = Field(description="The industry of the company")
class SocialInfo(BaseModel):
twitter: Optional[str] = Field(None, description="The Twitter username of the person")
linkedin: Optional[str] = Field(None, description="The LinkedIn username of the person")
facebook: Optional[str] = Field(None, description="The Facebook username of the person")
instagram: Optional[str] = Field(None, description="The Instagram username of the person")
class RowEnrichmentResult(BaseModel):
basicInfo: BasicInfo
companyInfo: CompanyInfo
socialInfo: SocialInfo
def complete_wait_token(wait_token: WaitToken, result: dict):
"""Send the enrichment result back to Trigger.dev"""
with trace("complete_wait_token"):
url = f"{os.environ['TRIGGER_API_URL']}/api/v1/waitpoints/tokens/{wait_token.id}/complete"
headers = {
"Authorization": f"Bearer {wait_token.publicAccessToken}",
"Content-Type": "application/json"
}
response = requests.post(url, json={"data": result}, headers=headers)
response.raise_for_status()
return response.json()
basic_info_agent = Agent(
name="Basic Info Agent",
instructions=f"""You are an expert at extracting basic information from a person's contact details.
""",
tools=[WebSearchTool()],
output_type=BasicInfo
)
company_info_agent = Agent(
name="Company Info Agent",
instructions=f"""You are an expert at extracting company information from a person's contact details.
""",
tools=[WebSearchTool()],
output_type=CompanyInfo
)
social_info_agent = Agent(
name="Social Info Agent",
instructions=f"""You are an expert at extracting social media information from a person's contact details.
""",
tools=[WebSearchTool()],
output_type=SocialInfo
)
agent = Agent(
name="CSV Row Enrichment Agent",
instructions=f"""You are an expert at enriching contact information data.
Your task is to use web search to find additional information about a person
based on their basic contact details.
Please find the basic information, company information, and social media information for the person.
The input data is from an unspecified CSV dataset, but most likely a CSV dump from a CRM or other source like Mailchimp, etc.
You'll receive the header row and a single row from the dataset to enrich, in their raw form.
Only include information you are confident about from reliable sources.
Use null for fields where you cannot find reliable information.
""",
tools=[
basic_info_agent.as_tool(
tool_name="basic_info_agent",
tool_description="Extract basic information from a person's contact details"
),
company_info_agent.as_tool(
tool_name="company_info_agent",
tool_description="Extract company information from a person's contact details"
),
social_info_agent.as_tool(
tool_name="social_info_agent",
tool_description="Extract social media information from a person's contact details"
)
],
output_type=RowEnrichmentResult
)
async def main(agent_input: AgentInput):
# Run the agent
result = await Runner.run(
agent,
f"""
Header row: {agent_input.row.header}
Row to enrich: {agent_input.row.row}
CSV URL: {agent_input.row.url}
"""
)
enriched_data = result.final_output
if isinstance(enriched_data, BaseModel):
enriched_data = enriched_data.model_dump()
print("Final Output:")
# Pretty print the final output
print(json.dumps(enriched_data, indent=2))
if not agent_input.disableWaitTokenCompletion:
try:
# Send the result back to Trigger.dev
complete_wait_token(agent_input.waitToken, enriched_data)
print("Successfully enriched data and notified Trigger.dev")
except json.JSONDecodeError:
print("Error: Agent output was not valid JSON")
sys.exit(1)
# Make sure to flush the logfire context
logfire.force_flush()
if __name__ == "__main__":
# Parse command line input as JSON
if len(sys.argv) < 2:
print("Usage: python agent.py '<json_input>'")
sys.exit(1)
# Extract the traceparent os.environ['TRACEPARENT']
carrier ={'traceparent': os.environ['TRACEPARENT']}
ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
with attach_context(carrier=carrier, third_party=True):
# Parse the input JSON into our Pydantic model
input_data = AgentInput.model_validate_json(sys.argv[1])
asyncio.run(main(input_data))
+40
View File
@@ -0,0 +1,40 @@
import { z } from "zod";
export const AgentLoopMetadata = z.object({
waitToken: z.object({
id: z.string(),
publicAccessToken: z.string(),
}),
});
export type AgentLoopMetadata = z.infer<typeof AgentLoopMetadata>;
export const CSVRowPayload = z.object({
header: z.string().describe("The header of the CSV dataset"),
row: z.string().describe("The row to handle"),
url: z.string().url().describe("The URL of the CSV dataset"),
});
export type CSVRowPayload = z.infer<typeof CSVRowPayload>;
export const RowEnrichmentResult = z.object({
basicInfo: z.object({
name: z.string().describe("The name of the row"),
email: z.string().email().describe("The email of the row"),
firstName: z.string().optional().describe("The first name of the row"),
lastName: z.string().optional().describe("The last name of the row"),
preferredName: z.string().optional().describe("The preferred name of the row"),
}),
companyInfo: z.object({
name: z.string().describe("The name of the company"),
industry: z.string().describe("The industry of the company"),
}),
socialInfo: z.object({
twitter: z.string().url().optional().describe("The Twitter URL of the person"),
linkedin: z.string().url().optional().describe("The LinkedIn URL of the person"),
facebook: z.string().url().optional().describe("The Facebook URL of the person"),
instagram: z.string().url().optional().describe("The Instagram URL of the person"),
}),
});
export type RowEnrichmentResult = z.infer<typeof RowEnrichmentResult>;
+20
View File
@@ -0,0 +1,20 @@
import { defineConfig } from "@trigger.dev/sdk";
import { pythonExtension } from "@trigger.dev/python/extension";
import { installPlaywrightChromium } from "./src/extensions/playwright";
export default defineConfig({
project: "proj_wkbbtayxrmeyqhankehb",
dirs: ["./src/trigger"],
maxDuration: 3600,
build: {
extensions: [
// This is required to use the Python extension
pythonExtension({
requirementsFile: "./requirements.txt", // Optional: Path to your requirements file
devPythonBinaryPath: `.venv/bin/python`, // Optional: Custom Python binary path
scripts: ["src/trigger/python/**/*.py"], // List of Python scripts to include
}),
installPlaywrightChromium(),
],
},
});