@standardagents/builder 0.11.0-next.ab7e1ea → 0.11.0-next.c3b4490

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/plugin.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import fs2 from 'fs';
2
2
  import path3 from 'path';
3
3
  import { fileURLToPath } from 'url';
4
- import { createRequire } from 'module';
5
4
 
6
5
  // src/plugin.ts
7
6
  var TSCONFIG_CONTENT = `{
@@ -398,6 +397,9 @@ declare module 'virtual:@standardagents/builder' {
398
397
  listThreads(params?: {
399
398
  agent_name?: string;
400
399
  user_id?: string;
400
+ search?: string;
401
+ startDate?: number;
402
+ endDate?: number;
401
403
  limit?: number;
402
404
  offset?: number;
403
405
  }): Promise<{ threads: ThreadRegistryEntry[]; total: number }>;
@@ -579,14 +581,91 @@ function needsRegeneration(config) {
579
581
  return false;
580
582
  }
581
583
 
584
+ // src/utils/model-parser.ts
585
+ function getName(content) {
586
+ return content.match(/name:\s*['"]([^'"]+)['"]/)?.[1];
587
+ }
588
+ function getProvider(content) {
589
+ const stringMatch = content.match(/provider:\s*['"]([^'"]+)['"]/)?.[1];
590
+ if (stringMatch) return stringMatch;
591
+ const refMatch = content.match(/provider:\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*[,\n}]/)?.[1];
592
+ return refMatch || void 0;
593
+ }
594
+ function getModel(content) {
595
+ return content.match(/model:\s*['"]([^'"]+)['"]/)?.[1];
596
+ }
597
+ function getInputPrice(content) {
598
+ const match = content.match(/inputPrice:\s*([\d.]+)/);
599
+ return match ? parseFloat(match[1]) : void 0;
600
+ }
601
+ function getOutputPrice(content) {
602
+ const match = content.match(/outputPrice:\s*([\d.]+)/);
603
+ return match ? parseFloat(match[1]) : void 0;
604
+ }
605
+ function getCachedPrice(content) {
606
+ const match = content.match(/cachedPrice:\s*([\d.]+)/);
607
+ return match ? parseFloat(match[1]) : void 0;
608
+ }
609
+ function getIncludedProviders(content) {
610
+ const match = content.match(/includedProviders:\s*\[([^\]]*)\]/);
611
+ if (!match) return void 0;
612
+ const items = match[1].match(/['"]([^'"]+)['"]/g);
613
+ return items ? items.map((s) => s.replace(/['"]/g, "")) : [];
614
+ }
615
+ function getFallbacks(content) {
616
+ const match = content.match(/fallbacks:\s*\[([^\]]*)\]/);
617
+ if (!match) return [];
618
+ const items = match[1].match(/['"]([^'"]+)['"]/g);
619
+ return items ? items.map((s) => s.replace(/['"]/g, "")) : [];
620
+ }
621
+ function getProviderTools(content) {
622
+ const match = content.match(/providerTools:\s*\[([^\]]*)\]/);
623
+ if (!match) return [];
624
+ const items = match[1].match(/['"]([^'"]+)['"]/g);
625
+ return items ? items.map((s) => s.replace(/['"]/g, "")) : [];
626
+ }
627
+ function getCapabilities(content) {
628
+ const match = content.match(/capabilities:\s*\{([^}]*)\}/s);
629
+ if (!match) return void 0;
630
+ const inner = match[1];
631
+ const caps = {};
632
+ const boolMatches = inner.matchAll(/(\w+):\s*(true|false)/g);
633
+ for (const m of boolMatches) {
634
+ caps[m[1]] = m[2] === "true";
635
+ }
636
+ const numMatches = inner.matchAll(/(\w+):\s*(\d+)/g);
637
+ for (const m of numMatches) {
638
+ caps[m[1]] = parseInt(m[2], 10);
639
+ }
640
+ return Object.keys(caps).length > 0 ? caps : void 0;
641
+ }
642
+ function parseModelFile(content) {
643
+ const name = getName(content);
644
+ if (!name) return null;
645
+ return {
646
+ name,
647
+ provider: getProvider(content),
648
+ model: getModel(content),
649
+ inputPrice: getInputPrice(content),
650
+ outputPrice: getOutputPrice(content),
651
+ cachedPrice: getCachedPrice(content),
652
+ includedProviders: getIncludedProviders(content),
653
+ fallbacks: getFallbacks(content),
654
+ providerTools: getProviderTools(content),
655
+ capabilities: getCapabilities(content)
656
+ };
657
+ }
658
+
582
659
  // src/sdk/generators/generateModelFile.ts
583
- function generateModelFile(data) {
660
+ function generateModelFile(data, options) {
661
+ const { providerName, providerPackage } = options;
584
662
  const lines = [
585
663
  `import { defineModel } from '@standardagents/builder';`,
664
+ `import { ${providerName} } from '${providerPackage}';`,
586
665
  "",
587
666
  `export default defineModel({`,
588
667
  ` name: '${escapeString(data.name)}',`,
589
- ` provider: '${escapeString(data.provider)}',`,
668
+ ` provider: ${providerName},`,
590
669
  ` model: '${escapeString(data.model)}',`
591
670
  ];
592
671
  if (data.includedProviders && data.includedProviders.length > 0) {
@@ -595,6 +674,9 @@ function generateModelFile(data) {
595
674
  if (data.fallbacks && data.fallbacks.length > 0) {
596
675
  lines.push(` fallbacks: ${JSON.stringify(data.fallbacks)},`);
597
676
  }
677
+ if (data.providerTools && data.providerTools.length > 0) {
678
+ lines.push(` providerTools: ${JSON.stringify(data.providerTools)},`);
679
+ }
598
680
  if (data.inputPrice !== void 0) {
599
681
  lines.push(` inputPrice: ${data.inputPrice},`);
600
682
  }
@@ -604,6 +686,36 @@ function generateModelFile(data) {
604
686
  if (data.cachedPrice !== void 0) {
605
687
  lines.push(` cachedPrice: ${data.cachedPrice},`);
606
688
  }
689
+ if (data.capabilities && Object.keys(data.capabilities).length > 0) {
690
+ const caps = data.capabilities;
691
+ const capLines = [];
692
+ if (caps.supportsImages !== void 0) {
693
+ capLines.push(` supportsImages: ${caps.supportsImages},`);
694
+ }
695
+ if (caps.supportsToolCalls !== void 0) {
696
+ capLines.push(` supportsToolCalls: ${caps.supportsToolCalls},`);
697
+ }
698
+ if (caps.supportsStreaming !== void 0) {
699
+ capLines.push(` supportsStreaming: ${caps.supportsStreaming},`);
700
+ }
701
+ if (caps.supportsJsonMode !== void 0) {
702
+ capLines.push(` supportsJsonMode: ${caps.supportsJsonMode},`);
703
+ }
704
+ if (caps.maxContextTokens !== void 0) {
705
+ capLines.push(` maxContextTokens: ${caps.maxContextTokens},`);
706
+ }
707
+ if (caps.maxOutputTokens !== void 0) {
708
+ capLines.push(` maxOutputTokens: ${caps.maxOutputTokens},`);
709
+ }
710
+ if (caps.reasoningLevels !== void 0) {
711
+ capLines.push(` reasoningLevels: ${JSON.stringify(caps.reasoningLevels)},`);
712
+ }
713
+ if (capLines.length > 0) {
714
+ lines.push(` capabilities: {`);
715
+ lines.push(...capLines);
716
+ lines.push(` },`);
717
+ }
718
+ }
607
719
  lines.push(`});`);
608
720
  lines.push("");
609
721
  return lines.join("\n");
@@ -813,16 +925,6 @@ function generatePromptFile(data) {
813
925
  const toolsCode = formatToolsArray(data.tools);
814
926
  lines.push(` tools: ${toolsCode},`);
815
927
  }
816
- if (data.handoffAgents && data.handoffAgents.length > 0) {
817
- const agentsStr = data.handoffAgents.map((a) => `'${escapeString2(a)}'`).join(", ");
818
- lines.push(` handoffAgents: [${agentsStr}],`);
819
- }
820
- if (data.beforeTool) {
821
- lines.push(` beforeTool: '${escapeString2(data.beforeTool)}',`);
822
- }
823
- if (data.afterTool) {
824
- lines.push(` afterTool: '${escapeString2(data.afterTool)}',`);
825
- }
826
928
  if (data.reasoning && hasNonNullProperties(data.reasoning)) {
827
929
  const reasoningCode = formatReasoningConfig(data.reasoning);
828
930
  lines.push(` reasoning: ${reasoningCode},`);
@@ -868,6 +970,12 @@ function formatToolConfig(config) {
868
970
  if (config.init_user_message_property !== void 0 && config.init_user_message_property !== null) {
869
971
  parts.push(`initUserMessageProperty: '${escapeString2(config.init_user_message_property)}'`);
870
972
  }
973
+ if (config.init_attachments_property !== void 0 && config.init_attachments_property !== null) {
974
+ parts.push(`initAttachmentsProperty: '${escapeString2(config.init_attachments_property)}'`);
975
+ }
976
+ if (config.tenvs && Object.keys(config.tenvs).length > 0) {
977
+ parts.push(`tenvs: ${JSON.stringify(config.tenvs)}`);
978
+ }
871
979
  return `{ ${parts.join(", ")} }`;
872
980
  }
873
981
  function formatReasoningConfig(reasoning) {
@@ -1000,8 +1108,8 @@ function generateAgentFile(data) {
1000
1108
  if (data.toolDescription) {
1001
1109
  lines.push(` toolDescription: '${escapeString3(data.toolDescription)}',`);
1002
1110
  }
1003
- if (data.tags && data.tags.length > 0) {
1004
- lines.push(` tags: ${JSON.stringify(data.tags)},`);
1111
+ if (data.tenvs && Object.keys(data.tenvs).length > 0) {
1112
+ lines.push(` tenvs: ${JSON.stringify(data.tenvs)},`);
1005
1113
  }
1006
1114
  lines.push(`});`);
1007
1115
  lines.push("");
@@ -1022,11 +1130,11 @@ function formatSideConfig(config) {
1022
1130
  if (config.stopToolResponseProperty) {
1023
1131
  parts.push(` stopToolResponseProperty: '${escapeString3(config.stopToolResponseProperty)}',`);
1024
1132
  }
1025
- if (config.maxTurns !== void 0) {
1026
- parts.push(` maxTurns: ${config.maxTurns},`);
1133
+ if (config.maxSteps !== void 0) {
1134
+ parts.push(` maxSteps: ${config.maxSteps},`);
1027
1135
  }
1028
- if (config.endConversationTool) {
1029
- parts.push(` endConversationTool: '${escapeString3(config.endConversationTool)}',`);
1136
+ if (config.endSessionTool) {
1137
+ parts.push(` endSessionTool: '${escapeString3(config.endSessionTool)}',`);
1030
1138
  }
1031
1139
  if (config.manualStopCondition !== void 0) {
1032
1140
  parts.push(` manualStopCondition: ${config.manualStopCondition},`);
@@ -1039,8 +1147,15 @@ function escapeString3(str) {
1039
1147
  }
1040
1148
 
1041
1149
  // src/sdk/persistence/index.ts
1150
+ var PROVIDER_PACKAGE_MAP = {
1151
+ openai: { name: "openai", package: "@standardagents/openai" },
1152
+ openrouter: { name: "openrouter", package: "@standardagents/openrouter" },
1153
+ anthropic: { name: "anthropic", package: "@standardagents/anthropic" },
1154
+ google: { name: "google", package: "@standardagents/google" },
1155
+ test: { name: "test", package: "@standardagents/builder/test" }
1156
+ };
1042
1157
  function nameToFilename(name) {
1043
- return name.replace(/[/\\]/g, "__").replace(/[:*?"<>|]/g, "_").replace(/-/g, "_");
1158
+ return name.replace(/[/\\]/g, "__").replace(/[:*?"<>|.]/g, "_").replace(/-/g, "_");
1044
1159
  }
1045
1160
  function getModelFilePath(modelsDir, name) {
1046
1161
  const filename = nameToFilename(name);
@@ -1062,7 +1177,17 @@ async function saveModel(modelsDir, data, overwrite = false) {
1062
1177
  error: `Model file already exists: ${filePath}. Use update to modify existing models.`
1063
1178
  };
1064
1179
  }
1065
- const content = generateModelFile(data);
1180
+ const providerInfo = PROVIDER_PACKAGE_MAP[data.provider];
1181
+ if (!providerInfo) {
1182
+ return {
1183
+ success: false,
1184
+ error: `Unknown provider '${data.provider}'. Must be one of: ${Object.keys(PROVIDER_PACKAGE_MAP).join(", ")}`
1185
+ };
1186
+ }
1187
+ const content = generateModelFile(data, {
1188
+ providerName: providerInfo.name,
1189
+ providerPackage: providerInfo.package
1190
+ });
1066
1191
  await fs2.promises.writeFile(filePath, content, "utf-8");
1067
1192
  return {
1068
1193
  success: true,
@@ -1126,7 +1251,7 @@ function validateModelData(data) {
1126
1251
  if (!data.provider || typeof data.provider !== "string") {
1127
1252
  return "Model provider is required and must be a string";
1128
1253
  }
1129
- const validProviders = ["openai", "openrouter", "anthropic", "google", "test"];
1254
+ const validProviders = Object.keys(PROVIDER_PACKAGE_MAP);
1130
1255
  if (!validProviders.includes(data.provider)) {
1131
1256
  return `Invalid provider '${data.provider}'. Must be one of: ${validProviders.join(", ")}`;
1132
1257
  }
@@ -1173,11 +1298,8 @@ function transformPromptData(data) {
1173
1298
  required_schema: "requiredSchema",
1174
1299
  include_chat: "includeChat",
1175
1300
  include_past_tools: "includePastTools",
1176
- before_tool: "beforeTool",
1177
- after_tool: "afterTool",
1178
1301
  parallel_tool_calls: "parallelToolCalls",
1179
1302
  tool_choice: "toolChoice",
1180
- handoff_agents: "handoffAgents",
1181
1303
  reasoning_effort: "reasoningEffort",
1182
1304
  reasoning_max_tokens: "reasoningMaxTokens",
1183
1305
  reasoning_exclude: "reasoningExclude",
@@ -1328,9 +1450,6 @@ function validatePromptData(data) {
1328
1450
  if (data.tools !== void 0 && !Array.isArray(data.tools)) {
1329
1451
  return "tools must be an array";
1330
1452
  }
1331
- if (data.handoffAgents !== void 0 && !Array.isArray(data.handoffAgents)) {
1332
- return "handoffAgents must be an array";
1333
- }
1334
1453
  if (data.reasoning !== void 0) {
1335
1454
  if (typeof data.reasoning !== "object") {
1336
1455
  return "reasoning must be an object";
@@ -1375,11 +1494,11 @@ function transformAgentData(data) {
1375
1494
  if (data.side_a_stop_tool_response_property) {
1376
1495
  transformed.sideA.stopToolResponseProperty = data.side_a_stop_tool_response_property;
1377
1496
  }
1378
- if (data.side_a_max_turns !== void 0) {
1379
- transformed.sideA.maxTurns = data.side_a_max_turns;
1497
+ if (data.side_a_max_steps !== void 0) {
1498
+ transformed.sideA.maxSteps = data.side_a_max_steps;
1380
1499
  }
1381
- if (data.side_a_end_conversation_tool) {
1382
- transformed.sideA.endConversationTool = data.side_a_end_conversation_tool;
1500
+ if (data.side_a_end_session_tool) {
1501
+ transformed.sideA.endSessionTool = data.side_a_end_session_tool;
1383
1502
  }
1384
1503
  if (data.side_a_manual_stop_condition !== void 0) {
1385
1504
  transformed.sideA.manualStopCondition = data.side_a_manual_stop_condition;
@@ -1400,11 +1519,11 @@ function transformAgentData(data) {
1400
1519
  if (data.side_b_stop_tool_response_property) {
1401
1520
  transformed.sideB.stopToolResponseProperty = data.side_b_stop_tool_response_property;
1402
1521
  }
1403
- if (data.side_b_max_turns !== void 0) {
1404
- transformed.sideB.maxTurns = data.side_b_max_turns;
1522
+ if (data.side_b_max_steps !== void 0) {
1523
+ transformed.sideB.maxSteps = data.side_b_max_steps;
1405
1524
  }
1406
- if (data.side_b_end_conversation_tool) {
1407
- transformed.sideB.endConversationTool = data.side_b_end_conversation_tool;
1525
+ if (data.side_b_end_session_tool) {
1526
+ transformed.sideB.endSessionTool = data.side_b_end_session_tool;
1408
1527
  }
1409
1528
  if (data.side_b_manual_stop_condition !== void 0) {
1410
1529
  transformed.sideB.manualStopCondition = data.side_b_manual_stop_condition;
@@ -1416,9 +1535,6 @@ function transformAgentData(data) {
1416
1535
  if (data.tool_description) {
1417
1536
  transformed.toolDescription = data.tool_description;
1418
1537
  }
1419
- if (data.tags) {
1420
- transformed.tags = data.tags;
1421
- }
1422
1538
  return transformed;
1423
1539
  }
1424
1540
  function getAgentFilePath(agentsDir, name) {
@@ -1606,9 +1722,9 @@ function validateAgentData(data) {
1606
1722
  if (data.sideA.stopTool && !data.sideA.stopToolResponseProperty) {
1607
1723
  return "sideA.stopToolResponseProperty is required when sideA.stopTool is set";
1608
1724
  }
1609
- if (data.sideA.maxTurns !== void 0) {
1610
- if (typeof data.sideA.maxTurns !== "number" || data.sideA.maxTurns <= 0) {
1611
- return "sideA.maxTurns must be a positive number";
1725
+ if (data.sideA.maxSteps !== void 0) {
1726
+ if (typeof data.sideA.maxSteps !== "number" || data.sideA.maxSteps <= 0) {
1727
+ return "sideA.maxSteps must be a positive number";
1612
1728
  }
1613
1729
  }
1614
1730
  if (data.type === "dual_ai") {
@@ -1621,9 +1737,9 @@ function validateAgentData(data) {
1621
1737
  if (data.sideB.stopTool && !data.sideB.stopToolResponseProperty) {
1622
1738
  return "sideB.stopToolResponseProperty is required when sideB.stopTool is set";
1623
1739
  }
1624
- if (data.sideB.maxTurns !== void 0) {
1625
- if (typeof data.sideB.maxTurns !== "number" || data.sideB.maxTurns <= 0) {
1626
- return "sideB.maxTurns must be a positive number";
1740
+ if (data.sideB.maxSteps !== void 0) {
1741
+ if (typeof data.sideB.maxSteps !== "number" || data.sideB.maxSteps <= 0) {
1742
+ return "sideB.maxSteps must be a positive number";
1627
1743
  }
1628
1744
  }
1629
1745
  }
@@ -1635,21 +1751,10 @@ function validateAgentData(data) {
1635
1751
  return "maxSessionTurns must be a positive number";
1636
1752
  }
1637
1753
  }
1638
- if (data.tags !== void 0) {
1639
- if (!Array.isArray(data.tags)) {
1640
- return "tags must be an array";
1641
- }
1642
- for (const tag of data.tags) {
1643
- if (typeof tag !== "string") {
1644
- return "Each tag must be a string";
1645
- }
1646
- }
1647
- }
1648
1754
  return null;
1649
1755
  }
1650
1756
 
1651
1757
  // src/plugin.ts
1652
- createRequire(import.meta.url);
1653
1758
  var VIRTUAL_TOOLS_ID = "virtual:@standardagents-tools";
1654
1759
  var RESOLVED_VIRTUAL_TOOLS_ID = "\0" + VIRTUAL_TOOLS_ID;
1655
1760
  var VIRTUAL_ROUTES_ID = "virtual:@standardagents-routes";
@@ -1668,6 +1773,10 @@ var VIRTUAL_PROMPTS_ID = "virtual:@standardagents-prompts";
1668
1773
  var RESOLVED_VIRTUAL_PROMPTS_ID = "\0" + VIRTUAL_PROMPTS_ID;
1669
1774
  var VIRTUAL_AGENTS_ID = "virtual:@standardagents-agents";
1670
1775
  var RESOLVED_VIRTUAL_AGENTS_ID = "\0" + VIRTUAL_AGENTS_ID;
1776
+ var VIRTUAL_EFFECTS_ID = "virtual:@standardagents-effects";
1777
+ var RESOLVED_VIRTUAL_EFFECTS_ID = "\0" + VIRTUAL_EFFECTS_ID;
1778
+ var VIRTUAL_PROVIDERS_ID = "virtual:@standardagents-providers";
1779
+ var RESOLVED_VIRTUAL_PROVIDERS_ID = "\0" + VIRTUAL_PROVIDERS_ID;
1671
1780
  var VIRTUAL_BUILDER_ID = "virtual:@standardagents/builder";
1672
1781
  var RESOLVED_VIRTUAL_BUILDER_ID = "\0" + VIRTUAL_BUILDER_ID;
1673
1782
  function scanApiDirectory(dir, baseRoute = "") {
@@ -1843,6 +1952,33 @@ async function scanPromptsDirectory(dir) {
1843
1952
  async function scanAgentsDirectory(dir) {
1844
1953
  return scanConfigDirectory(dir, /export\s+default\s+defineAgent/);
1845
1954
  }
1955
+ async function scanEffectsDirectory(dir) {
1956
+ const effects = [];
1957
+ if (!fs2.existsSync(dir)) {
1958
+ return effects;
1959
+ }
1960
+ const entries = await fs2.promises.readdir(dir, { withFileTypes: true });
1961
+ for (const entry of entries) {
1962
+ if (entry.isFile() && entry.name.endsWith(".ts")) {
1963
+ const fileName = entry.name.replace(".ts", "");
1964
+ const filePath = path3.join(dir, entry.name);
1965
+ const importPath = "./" + path3.relative(process.cwd(), filePath).replace(/\\/g, "/");
1966
+ if (fileName === "CLAUDE" || fileName.startsWith("_")) {
1967
+ continue;
1968
+ }
1969
+ try {
1970
+ const content = fs2.readFileSync(filePath, "utf-8");
1971
+ if (!content.includes("defineEffect")) {
1972
+ continue;
1973
+ }
1974
+ } catch {
1975
+ continue;
1976
+ }
1977
+ effects.push({ name: fileName, importPath });
1978
+ }
1979
+ }
1980
+ return effects;
1981
+ }
1846
1982
  function parseRequestBody(req) {
1847
1983
  return new Promise((resolve, reject) => {
1848
1984
  let body = "";
@@ -1873,6 +2009,7 @@ function agentbuilder(options = {}) {
1873
2009
  const modelsDir = options.modelsDir ? path3.resolve(process.cwd(), options.modelsDir) : path3.resolve(process.cwd(), "agents/models");
1874
2010
  const promptsDir = options.promptsDir ? path3.resolve(process.cwd(), options.promptsDir) : path3.resolve(process.cwd(), "agents/prompts");
1875
2011
  const agentsDir = options.agentsDir ? path3.resolve(process.cwd(), options.agentsDir) : path3.resolve(process.cwd(), "agents/agents");
2012
+ const effectsDir = options.effectsDir ? path3.resolve(process.cwd(), options.effectsDir) : path3.resolve(process.cwd(), "agents/effects");
1876
2013
  const outputDir = path3.resolve(process.cwd(), ".agents");
1877
2014
  const typeGenConfig = {
1878
2015
  modelsDir,
@@ -2072,7 +2209,15 @@ function agentbuilder(options = {}) {
2072
2209
  "zod",
2073
2210
  "openai"
2074
2211
  ];
2212
+ const currentDir = path3.dirname(fileURLToPath(import.meta.url));
2213
+ const isInDist = currentDir.endsWith("dist");
2214
+ const builderClientDir = path3.resolve(
2215
+ currentDir,
2216
+ isInDist ? "./client" : "../dist/client"
2217
+ );
2075
2218
  return {
2219
+ // Set publicDir to builder's client assets so Cloudflare plugin preserves assets config
2220
+ publicDir: fs2.existsSync(builderClientDir) ? builderClientDir : void 0,
2076
2221
  optimizeDeps: {
2077
2222
  // Exclude our packages from pre-bundling - they contain cloudflare:workers imports
2078
2223
  // that cannot be resolved during dependency optimization
@@ -2108,6 +2253,9 @@ function agentbuilder(options = {}) {
2108
2253
  ];
2109
2254
  const depsToInclude = [
2110
2255
  "zod",
2256
+ "zod/v3",
2257
+ "zod/v4",
2258
+ "zod/v4/core",
2111
2259
  "openai"
2112
2260
  ];
2113
2261
  config.optimizeDeps = config.optimizeDeps || {};
@@ -2145,6 +2293,12 @@ function agentbuilder(options = {}) {
2145
2293
  if (id === VIRTUAL_AGENTS_ID) {
2146
2294
  return RESOLVED_VIRTUAL_AGENTS_ID;
2147
2295
  }
2296
+ if (id === VIRTUAL_EFFECTS_ID) {
2297
+ return RESOLVED_VIRTUAL_EFFECTS_ID;
2298
+ }
2299
+ if (id === VIRTUAL_PROVIDERS_ID) {
2300
+ return RESOLVED_VIRTUAL_PROVIDERS_ID;
2301
+ }
2148
2302
  if (id === VIRTUAL_BUILDER_ID) {
2149
2303
  return RESOLVED_VIRTUAL_BUILDER_ID;
2150
2304
  }
@@ -2220,9 +2374,56 @@ function isPublicRoute(routePath) {
2220
2374
  return true;
2221
2375
  }
2222
2376
 
2377
+ // Provider icon routes are public (used by <img src>)
2378
+ if (routePath.startsWith('/api/providers/') && routePath.includes('/icon')) {
2379
+ return true;
2380
+ }
2381
+
2223
2382
  return false;
2224
2383
  }
2225
2384
 
2385
+ // CORS headers for API responses
2386
+ const CORS_HEADERS = {
2387
+ "Access-Control-Allow-Origin": "*",
2388
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
2389
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With",
2390
+ "Access-Control-Max-Age": "86400",
2391
+ };
2392
+
2393
+ // Helper to create headers with CORS
2394
+ function corsHeaders(contentType) {
2395
+ return {
2396
+ "Content-Type": contentType,
2397
+ ...CORS_HEADERS,
2398
+ };
2399
+ }
2400
+
2401
+ // Helper to add CORS headers to any Response without touching the body
2402
+ function addCorsHeaders(response) {
2403
+ // Skip WebSocket upgrade responses - they can't be wrapped
2404
+ if (response.status === 101) {
2405
+ return response;
2406
+ }
2407
+
2408
+ // Skip if already has CORS headers
2409
+ if (response.headers.has("Access-Control-Allow-Origin")) {
2410
+ return response;
2411
+ }
2412
+
2413
+ // Create new headers with CORS added
2414
+ const newHeaders = new Headers(response.headers);
2415
+ for (const [key, value] of Object.entries(CORS_HEADERS)) {
2416
+ newHeaders.set(key, value);
2417
+ }
2418
+
2419
+ // Return new Response with same body stream (not cloned, just transferred)
2420
+ return new Response(response.body, {
2421
+ status: response.status,
2422
+ statusText: response.statusText,
2423
+ headers: newHeaders,
2424
+ });
2425
+ }
2426
+
2226
2427
  export async function router(request, env) {
2227
2428
  const url = new URL(request.url);
2228
2429
  const pathname = url.pathname;
@@ -2232,6 +2433,14 @@ export async function router(request, env) {
2232
2433
  return null;
2233
2434
  }
2234
2435
 
2436
+ // Handle CORS preflight requests
2437
+ if (request.method === "OPTIONS") {
2438
+ return new Response(null, {
2439
+ status: 204,
2440
+ headers: CORS_HEADERS,
2441
+ });
2442
+ }
2443
+
2235
2444
  // Strip mount point prefix for route matching, ensuring we keep the leading slash
2236
2445
  let routePath = pathname.slice(MOUNT_POINT.length) || "/";
2237
2446
  if (!routePath.startsWith('/')) {
@@ -2266,7 +2475,7 @@ ${threadRouteCode}
2266
2475
 
2267
2476
  // If requireAuth returns a Response, it's an error (401)
2268
2477
  if (authResult instanceof Response) {
2269
- return authResult;
2478
+ return addCorsHeaders(authResult);
2270
2479
  }
2271
2480
 
2272
2481
  authContext = authResult;
@@ -2285,16 +2494,17 @@ ${threadRouteCode}
2285
2494
  const result = await controller(context);
2286
2495
 
2287
2496
  if (result instanceof Response) {
2288
- return result;
2497
+ return addCorsHeaders(result);
2289
2498
  }
2290
2499
  if (typeof result === "string") {
2291
2500
  return new Response(result, {
2292
- headers: {
2293
- "Content-Type": "text/plain",
2294
- },
2501
+ headers: corsHeaders("text/plain"),
2295
2502
  });
2296
2503
  }
2297
- return Response.json(result);
2504
+ // JSON responses get CORS headers
2505
+ return new Response(JSON.stringify(result), {
2506
+ headers: corsHeaders("application/json"),
2507
+ });
2298
2508
  }
2299
2509
 
2300
2510
  // Serve UI for all other routes (SPA fallback)
@@ -2309,13 +2519,15 @@ async function serveUI(pathname, env) {
2309
2519
  // Create a proper request for the asset path
2310
2520
  // Use a dummy origin since we only care about the path
2311
2521
  // Re-add mount point since pathname was stripped by router
2312
- const assetUrl = \`http://localhost\${MOUNT_POINT}\${pathname}\`;
2522
+ // Handle root mountPoint "/" specially to avoid double slashes
2523
+ const mountPrefix = MOUNT_POINT === "/" ? "" : MOUNT_POINT;
2524
+ const assetUrl = \`http://localhost\${mountPrefix}\${pathname}\`;
2313
2525
  let response = await env.ASSETS.fetch(assetUrl);
2314
2526
 
2315
2527
  // If not found, fall back to index.html for SPA routing
2316
2528
  const isIndexHtml = response.status === 404 || pathname === "/" || !pathname.includes(".");
2317
2529
  if (isIndexHtml) {
2318
- response = await env.ASSETS.fetch(\`http://localhost\${MOUNT_POINT}/index.html\`);
2530
+ response = await env.ASSETS.fetch(\`http://localhost\${mountPrefix}/index.html\`);
2319
2531
 
2320
2532
  // Transform HTML to use configured mount point
2321
2533
  if (response.status === 200) {
@@ -2462,6 +2674,55 @@ ${agentsCode}
2462
2674
  };
2463
2675
 
2464
2676
  export const agentNames = ${JSON.stringify(agents.filter((a) => !a.error).map((a) => a.name))};
2677
+ `;
2678
+ }
2679
+ if (id === RESOLVED_VIRTUAL_EFFECTS_ID) {
2680
+ const effects = await scanEffectsDirectory(effectsDir);
2681
+ const effectsCode = effects.map(({ name, importPath, error }) => {
2682
+ if (error) {
2683
+ const escapedError = error.replace(/"/g, '\\"').replace(/\n/g, "\\n");
2684
+ return ` "${name}": async () => { throw new Error("${escapedError}"); },`;
2685
+ } else {
2686
+ return ` "${name}": async () => {
2687
+ try {
2688
+ return (await import("${importPath}")).default;
2689
+ } catch (error) {
2690
+ console.error('Failed to import effect ${name}:', error);
2691
+ throw error;
2692
+ }
2693
+ },`;
2694
+ }
2695
+ }).join("\n");
2696
+ return `// Virtual agent effects module
2697
+ export const effects = {
2698
+ ${effectsCode}
2699
+ };
2700
+
2701
+ export const effectNames = ${JSON.stringify(effects.filter((e) => !e.error).map((e) => e.name))};
2702
+ `;
2703
+ }
2704
+ if (id === RESOLVED_VIRTUAL_PROVIDERS_ID) {
2705
+ const firstPartyProviders = [
2706
+ { name: "openai", package: "@standardagents/openai", label: "OpenAI", envKey: "OPENAI_API_KEY" },
2707
+ { name: "openrouter", package: "@standardagents/openrouter", label: "OpenRouter", envKey: "OPENROUTER_API_KEY" }
2708
+ ];
2709
+ const customProviders = (options.providers || []).map((pkg) => ({
2710
+ name: pkg.split("/").pop() || pkg,
2711
+ package: pkg,
2712
+ label: pkg.split("/").pop() || pkg,
2713
+ envKey: `${(pkg.split("/").pop() || pkg).toUpperCase().replace(/-/g, "_")}_API_KEY`,
2714
+ isCustom: true
2715
+ }));
2716
+ const allProviders = [...firstPartyProviders, ...customProviders];
2717
+ return `// Virtual providers module - lists available LLM provider packages
2718
+ export const providers = ${JSON.stringify(allProviders, null, 2)};
2719
+
2720
+ export const providerNames = ${JSON.stringify(allProviders.map((p) => p.name))};
2721
+
2722
+ // Provider factories - dynamic imports for code splitting
2723
+ export const providerFactories = {
2724
+ ${allProviders.map((p) => ` "${p.name}": async () => (await import("${p.package}")).${p.name},`).join("\n")}
2725
+ };
2465
2726
  `;
2466
2727
  }
2467
2728
  if (id === RESOLVED_VIRTUAL_BUILDER_ID) {
@@ -2470,6 +2731,7 @@ export const agentNames = ${JSON.stringify(agents.filter((a) => !a.error).map((a
2470
2731
  const models = await scanModelsDirectory(modelsDir);
2471
2732
  const prompts = await scanPromptsDirectory(promptsDir);
2472
2733
  const agents = await scanAgentsDirectory(agentsDir);
2734
+ const effects = await scanEffectsDirectory(effectsDir);
2473
2735
  const toAbsolutePath = (relativePath) => {
2474
2736
  if (relativePath.startsWith("./")) {
2475
2737
  return path3.resolve(process.cwd(), relativePath).replace(/\\/g, "/");
@@ -2548,6 +2810,22 @@ export const agentNames = ${JSON.stringify(agents.filter((a) => !a.error).map((a
2548
2810
  console.error('Failed to import agent ${name}:', error);
2549
2811
  throw error;
2550
2812
  }
2813
+ },`;
2814
+ }
2815
+ }).join("\n");
2816
+ const effectsCode = effects.map(({ name, importPath, error }) => {
2817
+ const absPath = toAbsolutePath(importPath);
2818
+ if (error) {
2819
+ const escapedError = error.replace(/"/g, '\\"').replace(/\n/g, "\\n");
2820
+ return ` "${name}": async () => { throw new Error("${escapedError}"); },`;
2821
+ } else {
2822
+ return ` "${name}": async () => {
2823
+ try {
2824
+ return (await import("${absPath}")).default;
2825
+ } catch (error) {
2826
+ console.error('Failed to import effect ${name}:', error);
2827
+ throw error;
2828
+ }
2551
2829
  },`;
2552
2830
  }
2553
2831
  }).join("\n");
@@ -2558,7 +2836,8 @@ import { DurableAgentBuilder as _BaseDurableAgentBuilder } from '@standardagents
2558
2836
 
2559
2837
  // Import sip WASM module and initializer
2560
2838
  // Static import allows workerd to pre-compile the WASM at bundle time
2561
- import _sipWasm from '@standardagents/sip/dist/sip.wasm';
2839
+ // WASM is bundled in builder's dist to avoid transitive dependency resolution issues
2840
+ import _sipWasm from '@standardagents/builder/dist/sip.wasm';
2562
2841
  import { initWithWasmModule as _initSipWasm } from '@standardagents/sip';
2563
2842
 
2564
2843
  // Re-export router from virtual:@standardagents-routes
@@ -2585,6 +2864,10 @@ const _agents = {
2585
2864
  ${agentsCode}
2586
2865
  };
2587
2866
 
2867
+ const _effects = {
2868
+ ${effectsCode}
2869
+ };
2870
+
2588
2871
  /**
2589
2872
  * DurableThread with all virtual module methods already implemented.
2590
2873
  * Simply extend this class in your agents/Thread.ts file.
@@ -2619,6 +2902,10 @@ export class DurableThread extends _BaseDurableThread {
2619
2902
  agents() {
2620
2903
  return _agents;
2621
2904
  }
2905
+
2906
+ effects() {
2907
+ return _effects;
2908
+ }
2622
2909
  }
2623
2910
 
2624
2911
  /**
@@ -2645,6 +2932,10 @@ export class DurableAgentBuilder extends _BaseDurableAgentBuilder {
2645
2932
  agents() {
2646
2933
  return _agents;
2647
2934
  }
2935
+
2936
+ effects() {
2937
+ return _effects;
2938
+ }
2648
2939
  }
2649
2940
  `;
2650
2941
  }
@@ -2657,6 +2948,7 @@ export class DurableAgentBuilder extends _BaseDurableAgentBuilder {
2657
2948
  this.addWatchFile(modelsDir);
2658
2949
  this.addWatchFile(promptsDir);
2659
2950
  this.addWatchFile(agentsDir);
2951
+ this.addWatchFile(effectsDir);
2660
2952
  },
2661
2953
  configureServer(server) {
2662
2954
  server.watcher.on("add", async (file) => {
@@ -2684,51 +2976,26 @@ export class DurableAgentBuilder extends _BaseDurableAgentBuilder {
2684
2976
  try {
2685
2977
  const filePath = path3.join(modelsDir, file);
2686
2978
  const content = fs4.readFileSync(filePath, "utf-8");
2687
- const getName = (c) => c.match(/name:\s*['"]([^'"]+)['"]/)?.[1];
2688
- const getProvider = (c) => c.match(/provider:\s*['"]([^'"]+)['"]/)?.[1];
2689
- const getModel = (c) => c.match(/model:\s*['"]([^'"]+)['"]/)?.[1];
2690
- const getInputPrice = (c) => {
2691
- const match = c.match(/inputPrice:\s*([\d.]+)/);
2692
- return match ? parseFloat(match[1]) : void 0;
2693
- };
2694
- const getOutputPrice = (c) => {
2695
- const match = c.match(/outputPrice:\s*([\d.]+)/);
2696
- return match ? parseFloat(match[1]) : void 0;
2697
- };
2698
- const getCachedPrice = (c) => {
2699
- const match = c.match(/cachedPrice:\s*([\d.]+)/);
2700
- return match ? parseFloat(match[1]) : void 0;
2701
- };
2702
- const getIncludedProviders = (c) => {
2703
- const match = c.match(/includedProviders:\s*\[([^\]]*)\]/);
2704
- if (!match) return void 0;
2705
- const items = match[1].match(/['"]([^'"]+)['"]/g);
2706
- return items ? items.map((s) => s.replace(/['"]/g, "")) : [];
2707
- };
2708
- const getFallbacks = (c) => {
2709
- const match = c.match(/fallbacks:\s*\[([^\]]*)\]/);
2710
- if (!match) return [];
2711
- const items = match[1].match(/['"]([^'"]+)['"]/g);
2712
- return items ? items.map((s) => s.replace(/['"]/g, "")) : [];
2713
- };
2714
- const name = getName(content);
2715
- if (!name) return null;
2716
- const fallbacks = getFallbacks(content);
2717
- const fallbackObjects = fallbacks.map((fallbackName, index) => ({
2979
+ const parsed = parseModelFile(content);
2980
+ if (!parsed || !parsed.name) return null;
2981
+ const fallbackObjects = parsed.fallbacks.map((fallbackName, index) => ({
2718
2982
  id: fallbackName,
2719
2983
  name: fallbackName,
2720
2984
  order: index
2721
2985
  }));
2722
2986
  return {
2723
- id: name,
2724
- name,
2725
- provider: getProvider(content),
2726
- model: getModel(content),
2727
- input_price: getInputPrice(content),
2728
- output_price: getOutputPrice(content),
2729
- cached_price: getCachedPrice(content),
2730
- included_providers: getIncludedProviders(content),
2987
+ id: parsed.name,
2988
+ name: parsed.name,
2989
+ provider: parsed.provider,
2990
+ provider_id: parsed.provider,
2991
+ model: parsed.model,
2992
+ input_price: parsed.inputPrice,
2993
+ output_price: parsed.outputPrice,
2994
+ cached_price: parsed.cachedPrice,
2995
+ included_providers: parsed.includedProviders,
2731
2996
  fallbacks: fallbackObjects,
2997
+ providerTools: parsed.providerTools,
2998
+ capabilities: parsed.capabilities,
2732
2999
  created_at: Math.floor(Date.now() / 1e3)
2733
3000
  };
2734
3001
  } catch (error) {
@@ -2894,27 +3161,19 @@ export class DurableAgentBuilder extends _BaseDurableAgentBuilder {
2894
3161
  try {
2895
3162
  const filePath = path3.join(promptsDir, file);
2896
3163
  const content = fs4.readFileSync(filePath, "utf-8");
2897
- const getName = (c) => c.match(/name:\s*['"]([^'"]+)['"]/)?.[1];
3164
+ const getName2 = (c) => c.match(/name:\s*['"]([^'"]+)['"]/)?.[1];
2898
3165
  const getToolDescription = (c) => c.match(/toolDescription:\s*['"]([^'"]+)['"]/)?.[1];
2899
- const getModel = (c) => c.match(/model:\s*['"]([^'"]+)['"]/)?.[1];
3166
+ const getModel2 = (c) => c.match(/model:\s*['"]([^'"]+)['"]/)?.[1];
2900
3167
  const getIncludeChat = (c) => c.match(/includeChat:\s*(true|false)/)?.[1] === "true";
2901
3168
  const getIncludePastTools = (c) => c.match(/includePastTools:\s*(true|false)/)?.[1] === "true";
2902
3169
  const getParallelToolCalls = (c) => c.match(/parallelToolCalls:\s*(true|false)/)?.[1] === "true";
2903
3170
  const getToolChoice = (c) => c.match(/toolChoice:\s*['"]([^'"]+)['"]/)?.[1];
2904
- const getBeforeTool = (c) => c.match(/beforeTool:\s*['"]([^'"]+)['"]/)?.[1];
2905
- const getAfterTool = (c) => c.match(/afterTool:\s*['"]([^'"]+)['"]/)?.[1];
2906
3171
  const getTools = (c) => {
2907
3172
  const match = c.match(/tools:\s*\[([^\]]*)\]/);
2908
3173
  if (!match) return [];
2909
3174
  const items = match[1].match(/['"]([^'"]+)['"]/g);
2910
3175
  return items ? items.map((s) => s.replace(/['"]/g, "")) : [];
2911
3176
  };
2912
- const getHandoffAgents = (c) => {
2913
- const match = c.match(/handoffAgents:\s*\[([^\]]*)\]/);
2914
- if (!match) return [];
2915
- const items = match[1].match(/['"]([^'"]+)['"]/g);
2916
- return items ? items.map((s) => s.replace(/['"]/g, "")) : [];
2917
- };
2918
3177
  const getPrompt = (c) => {
2919
3178
  const backtickMatch = c.match(/prompt:\s*`([\s\S]*?)`/);
2920
3179
  if (backtickMatch) return backtickMatch[1];
@@ -2924,9 +3183,9 @@ export class DurableAgentBuilder extends _BaseDurableAgentBuilder {
2924
3183
  if (arrayMatch) return arrayMatch[1];
2925
3184
  return "";
2926
3185
  };
2927
- const name = getName(content);
3186
+ const name = getName2(content);
2928
3187
  if (!name) return null;
2929
- const modelId = getModel(content);
3188
+ const modelId = getModel2(content);
2930
3189
  const modelDef = modelId ? modelMap[modelId] : null;
2931
3190
  return {
2932
3191
  id: name,
@@ -2942,10 +3201,7 @@ export class DurableAgentBuilder extends _BaseDurableAgentBuilder {
2942
3201
  include_past_tools: getIncludePastTools(content),
2943
3202
  parallel_tool_calls: getParallelToolCalls(content),
2944
3203
  tool_choice: getToolChoice(content) || "auto",
2945
- before_tool: getBeforeTool(content) || null,
2946
- after_tool: getAfterTool(content) || null,
2947
3204
  tools: getTools(content),
2948
- prompts: getHandoffAgents(content),
2949
3205
  reasoning: null,
2950
3206
  // Complex to parse
2951
3207
  created_at: Math.floor(Date.now() / 1e3)
@@ -3100,7 +3356,7 @@ export class DurableAgentBuilder extends _BaseDurableAgentBuilder {
3100
3356
  try {
3101
3357
  const filePath = path3.join(agentsDir, file);
3102
3358
  const content = fs4.readFileSync(filePath, "utf-8");
3103
- const getName = (c) => c.match(/name:\s*['"]([^'"]+)['"]/)?.[1];
3359
+ const getName2 = (c) => c.match(/name:\s*['"]([^'"]+)['"]/)?.[1];
3104
3360
  const getTitle = (c) => c.match(/title:\s*['"]([^'"]+)['"]/)?.[1];
3105
3361
  const getType = (c) => c.match(/type:\s*['"]([^'"]+)['"]/)?.[1];
3106
3362
  const getDefaultPrompt = (c) => c.match(/defaultPrompt:\s*['"]([^'"]+)['"]/)?.[1];
@@ -3111,7 +3367,14 @@ export class DurableAgentBuilder extends _BaseDurableAgentBuilder {
3111
3367
  const items = match[1].match(/['"]([^'"]+)['"]/g);
3112
3368
  return items ? items.map((s) => s.replace(/['"]/g, "")) : [];
3113
3369
  };
3114
- const name = getName(content);
3370
+ const getSidePrompt = (c, side) => {
3371
+ const sideRegex = new RegExp(`${side}:\\s*\\{([^}]+)\\}`, "s");
3372
+ const sideMatch = c.match(sideRegex);
3373
+ if (!sideMatch) return null;
3374
+ const promptMatch = sideMatch[1].match(/prompt:\s*['"]([^'"]+)['"]/);
3375
+ return promptMatch ? promptMatch[1] : null;
3376
+ };
3377
+ const name = getName2(content);
3115
3378
  if (!name) return null;
3116
3379
  return {
3117
3380
  id: name,
@@ -3121,6 +3384,8 @@ export class DurableAgentBuilder extends _BaseDurableAgentBuilder {
3121
3384
  default_prompt: getDefaultPrompt(content) || "",
3122
3385
  default_model: getDefaultModel(content) || "",
3123
3386
  tools: getTools(content),
3387
+ side_a_agent_prompt: getSidePrompt(content, "sideA"),
3388
+ side_b_agent_prompt: getSidePrompt(content, "sideB"),
3124
3389
  created_at: Math.floor(Date.now() / 1e3)
3125
3390
  };
3126
3391
  } catch (error) {