veryfront 0.1.1209 → 0.1.1211
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/esm/cli/commands/eval/command.d.ts.map +1 -1
- package/esm/cli/commands/eval/command.js +75 -30
- package/esm/cli/templates/manifest.js +11 -11
- package/esm/deno.js +1 -1
- package/esm/src/agent/react/use-chat/streaming/handler.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/streaming/handler.js +35 -2
- package/esm/src/agent/react/use-chat/streaming/parts-builder.d.ts +12 -1
- package/esm/src/agent/react/use-chat/streaming/parts-builder.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/streaming/parts-builder.js +20 -5
- package/esm/src/agent/runtime/agent-loop-skill-state.d.ts +34 -0
- package/esm/src/agent/runtime/agent-loop-skill-state.d.ts.map +1 -0
- package/esm/src/agent/runtime/agent-loop-skill-state.js +59 -0
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +46 -75
- package/esm/src/chat/conversation.d.ts +4 -59
- package/esm/src/chat/conversation.d.ts.map +1 -1
- package/esm/src/chat/conversation.js +4 -662
- package/esm/src/chat/message-part-parsing.d.ts +68 -0
- package/esm/src/chat/message-part-parsing.d.ts.map +1 -0
- package/esm/src/chat/message-part-parsing.js +162 -0
- package/esm/src/chat/message-prep.js +3 -1
- package/esm/src/chat/part-field-access.d.ts +25 -0
- package/esm/src/chat/part-field-access.d.ts.map +1 -0
- package/esm/src/chat/part-field-access.js +52 -0
- package/esm/src/chat/provider-input-types.d.ts +32 -0
- package/esm/src/chat/provider-input-types.d.ts.map +1 -0
- package/esm/src/chat/provider-input-types.js +1 -0
- package/esm/src/chat/provider-message-conversion.d.ts +5 -0
- package/esm/src/chat/provider-message-conversion.d.ts.map +1 -0
- package/esm/src/chat/provider-message-conversion.js +291 -0
- package/esm/src/chat/tool-replay-reconciliation.d.ts +15 -0
- package/esm/src/chat/tool-replay-reconciliation.d.ts.map +1 -0
- package/esm/src/chat/tool-replay-reconciliation.js +200 -0
- package/esm/src/eval/metric-labels.d.ts +16 -0
- package/esm/src/eval/metric-labels.d.ts.map +1 -0
- package/esm/src/eval/metric-labels.js +91 -0
- package/esm/src/eval/metrics.d.ts.map +1 -1
- package/esm/src/eval/metrics.js +4 -0
- package/esm/src/eval/report.d.ts.map +1 -1
- package/esm/src/eval/report.js +8 -1
- package/esm/src/eval/run-report.d.ts +2 -0
- package/esm/src/eval/run-report.d.ts.map +1 -1
- package/esm/src/eval/run-report.js +1 -0
- package/esm/src/eval/types.d.ts +9 -0
- package/esm/src/eval/types.d.ts.map +1 -1
- package/esm/src/extensions/eval/eval-report-exporter.d.ts +4 -1
- package/esm/src/extensions/eval/eval-report-exporter.d.ts.map +1 -1
- package/esm/src/extensions/eval/eval-report-exporter.js +17 -0
- package/esm/src/html/hydration-script-builder/hydration-runtime.generated.js +1 -1
- package/esm/src/integrations/feature-flags.d.ts +20 -0
- package/esm/src/integrations/feature-flags.d.ts.map +1 -1
- package/esm/src/integrations/feature-flags.js +35 -0
- package/esm/src/integrations/index.js +4 -4
- package/esm/src/modules/server/module-response.d.ts +46 -0
- package/esm/src/modules/server/module-response.d.ts.map +1 -0
- package/esm/src/modules/server/module-response.js +87 -0
- package/esm/src/modules/server/module-server.d.ts.map +1 -1
- package/esm/src/modules/server/module-server.js +31 -95
- package/esm/src/platform/compat/http/pinned-fetch.d.ts +1 -1
- package/esm/src/react/components/chat/chat/utils/message-parts.d.ts +2 -6
- package/esm/src/react/components/chat/chat/utils/message-parts.d.ts.map +1 -1
- package/esm/src/react/components/chat/chat/utils/message-parts.js +20 -0
- package/esm/src/release-assets/build-executor.d.ts +0 -1
- package/esm/src/release-assets/build-executor.d.ts.map +1 -1
- package/esm/src/release-assets/build-executor.js +0 -3
- package/esm/src/release-assets/index.d.ts +1 -1
- package/esm/src/release-assets/index.d.ts.map +1 -1
- package/esm/src/release-assets/manifest-cache.d.ts.map +1 -1
- package/esm/src/release-assets/manifest-cache.js +9 -2
- package/esm/src/release-assets/manifest-schema.d.ts +24 -3
- package/esm/src/release-assets/manifest-schema.d.ts.map +1 -1
- package/esm/src/release-assets/manifest-schema.js +26 -16
- package/esm/src/runtime/runtime-bridge.d.ts.map +1 -1
- package/esm/src/runtime/runtime-bridge.js +1 -0
- package/esm/src/server/handlers/dev/framework-candidates.generated.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/framework-candidates.generated.js +25 -1
- package/esm/src/server/handlers/dev/styles-css.handler.d.ts +17 -0
- package/esm/src/server/handlers/dev/styles-css.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/styles-css.handler.js +54 -8
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/eval/command.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAEL,KAAK,IAAI,EACT,KAAK,oBAAoB,EAE1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EACV,cAAc,EACd,uBAAuB,EAGvB,0BAA0B,EAC1B,UAAU,EAGV,sBAAsB,EACtB,sBAAsB,EACtB,YAAY,EACb,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAIL,KAAK,yBAAyB,EAC/B,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/eval/command.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAEL,KAAK,IAAI,EACT,KAAK,oBAAoB,EAE1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EACV,cAAc,EACd,uBAAuB,EAGvB,0BAA0B,EAC1B,UAAU,EAGV,sBAAsB,EACtB,sBAAsB,EACtB,YAAY,EACb,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAIL,KAAK,yBAAyB,EAC/B,MAAM,uCAAuC,CAAC;AAW/C,OAAO,EACL,2BAA2B,EAG5B,MAAM,6CAA6C,CAAC;AAUrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,WAAW,WAAY,SAAQ,QAAQ;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,uBAAuB;IAC/B,2BAA2B,CAAC,EAAE,OAAO,2BAA2B,CAAC;CAClE;AAED,KAAK,+BAA+B,GAAG;IACrC,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oBAAoB,EAAE,MAAM,CAAC;CAC9B,CAAC;AAOF,KAAK,6BAA6B,GAAG;IACnC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,CAAC;AAEF,KAAK,yBAAyB,GAAG,IAAI,CAAC,0BAA0B,EAAE,eAAe,CAAC,CAAC;AAyDnF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAErD;AAQD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAKhG;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CASjE;AA8GD,wBAAgB,oCAAoC,CAClD,MAAM,EAAE,UAAU,EAClB,YAAY,EAAE,+BAA+B,GAC5C,UAAU,CAiBZ;AAWD,wBAAsB,2BAA2B,CAC/C,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE,6BAAkC,GAC1C,OAAO,CAAC,+BAA+B,GAAG,SAAS,CAAC,CAgDtD;AAED,wBAAsB,8BAA8B,CAClD,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,GACnC,OAAO,CAAC,UAAU,CAAC,CAerB;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,UAAU,EAClB,MAAM,CAAC,EAAE,sBAAsB,GAC9B,OAAO,CAAC,UAAU,CAAC,CAGrB;AA8FD,wBAAsB,6BAA6B,CACjD,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,yBAAyB,CAAC,CA0CpC;AAED,wBAAsB,uCAAuC,CAC3D,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,iCAAiC,GAAG,SAAS,CAAC,CAKxD;AAYD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,KAAK,qBAAqB,GAAG,IAAI,CAAC,eAAe,EAAE,aAAa,GAAG,IAAI,CAAC,GAAG;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAQF,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,sEAQjD;AAaD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,aAAa,MA0DrD;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,aAAa,GAAG,YAAY,EAAE,CAY1E;AAyBD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,IACrD,qCAAqC,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmC3E;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,GAAE,oBAAyB,IACpE,SAAS,sBAAsB;;;;;;GAiB9C;AAmKD,KAAK,yBAAyB,GAAG;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,KAAK,iCAAiC,GAAG;IACvC,MAAM,EAAE,yBAAyB,CAAC;IAClC,MAAM,EAAE,yBAAyB,CAAC;CACnC,CAAC;AAuBF,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,GAAG,MAAM,EAAE,CAUxF;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,GAC1C,OAAO,CAGT;AA2BD,wBAAgB,iCAAiC,IAAI,yBAAyB,CAuB7E;AAqMD,wBAAsB,cAAc,CAClC,OAAO,EAAE,WAAW,EACpB,YAAY,GAAE,uBAA4B,GACzC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA2V7B;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAKrE"}
|
|
@@ -10,7 +10,8 @@ import { createLLMProviderRegistry, LLMProviderRegistryName } from "../../../src
|
|
|
10
10
|
import { exportEvalReport, resolveEvalRunProvenance, runEval } from "../../../src/eval/index.js";
|
|
11
11
|
import { getCurrentVeryfrontCloudContext, getVeryfrontCloudBootstrap, runWithVeryfrontCloudContextAsync, } from "../../../src/provider/index.js";
|
|
12
12
|
import { applyRuntimeAuthContext, resolveLinkedProjectSlug } from "../../shared/runtime-auth.js";
|
|
13
|
-
import {
|
|
13
|
+
import { brand, dim } from "../../ui/index.js";
|
|
14
|
+
import { cliLogger, exitProcess, isQuiet, VERSION } from "../../utils/index.js";
|
|
14
15
|
import { discoverProjectAgentRuntime, runWithProjectAgentRuntime, } from "../../../src/agent/project/agent-runtime.js";
|
|
15
16
|
import { runEvalReport } from "../../../src/eval/run-report.js";
|
|
16
17
|
import { createErrorEnvelope, createSuccessEnvelope, isJsonMode, outputJson, } from "../../shared/json-output.js";
|
|
@@ -612,16 +613,53 @@ function getDiscoveredEvals(runtime) {
|
|
|
612
613
|
};
|
|
613
614
|
});
|
|
614
615
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
616
|
+
/**
|
|
617
|
+
* Eval output is written straight to stdout rather than through `cliLogger`, because the logger
|
|
618
|
+
* stamps every line with its level glyph. Here the `●` is meaningful on its own: it marks the
|
|
619
|
+
* individual metric assertions, and nothing else, so a reader can find them without reading labels.
|
|
620
|
+
*
|
|
621
|
+
* Bypassing the logger also bypasses its level, so `--quiet` is honored here instead. These lines
|
|
622
|
+
* are informational, and quiet mode raises the logger to WARN precisely to drop that tier.
|
|
623
|
+
*/
|
|
624
|
+
function printLine(text) {
|
|
625
|
+
if (isQuiet())
|
|
626
|
+
return;
|
|
627
|
+
console.log(` ${text}`);
|
|
628
|
+
}
|
|
629
|
+
function printBlankLine() {
|
|
630
|
+
if (isQuiet())
|
|
631
|
+
return;
|
|
632
|
+
console.log("");
|
|
633
|
+
}
|
|
634
|
+
/** Width of "Target:", the longest header label, so the header values line up in one column. */
|
|
635
|
+
const HEADER_LABEL_WIDTH = "Target:".length + 1;
|
|
636
|
+
function printHeader(label, value) {
|
|
637
|
+
printLine(`${`${label}:`.padEnd(HEADER_LABEL_WIDTH)}${value}`);
|
|
638
|
+
}
|
|
639
|
+
/** Written artifacts are follow-up detail for the results above them, so they are dimmed. */
|
|
640
|
+
function printArtifact(label, path) {
|
|
641
|
+
printLine(dim(`${label}: ${path}`));
|
|
642
|
+
}
|
|
643
|
+
function formatPercent(rate) {
|
|
644
|
+
return `${Math.round(rate * 100)}%`;
|
|
645
|
+
}
|
|
646
|
+
function printReport(report, options = {}) {
|
|
647
|
+
printHeader("Eval", options.name ?? report.definitionId);
|
|
648
|
+
printHeader("Target", report.target);
|
|
649
|
+
printHeader("Result", `${report.summary.passed}/${report.summary.records} passed (${formatPercent(report.summary.passRate)})`);
|
|
650
|
+
if (report.summary.metrics.length > 0)
|
|
651
|
+
printBlankLine();
|
|
619
652
|
for (const metric of report.summary.metrics) {
|
|
620
|
-
|
|
653
|
+
const total = metric.passed + metric.failed;
|
|
654
|
+
printLine(`${brand("●")} ${metric.label ?? metric.name}: ${metric.passed}/${total} passed (${formatPercent(metric.passRate)})`);
|
|
621
655
|
}
|
|
656
|
+
const { baseline } = options;
|
|
657
|
+
const notes = (report.exports ?? []).length > 0 || baseline !== undefined;
|
|
658
|
+
if (notes)
|
|
659
|
+
printBlankLine();
|
|
622
660
|
for (const result of report.exports ?? []) {
|
|
623
661
|
if (result.ok) {
|
|
624
|
-
|
|
662
|
+
printLine(`Export ${result.exporterId}: ok`);
|
|
625
663
|
}
|
|
626
664
|
else {
|
|
627
665
|
cliLogger.warn(`Export ${result.exporterId}: failed: ${result.error}`);
|
|
@@ -629,7 +667,7 @@ function printReport(report, baseline) {
|
|
|
629
667
|
}
|
|
630
668
|
if (baseline) {
|
|
631
669
|
const direction = baseline.passRateDelta >= 0 ? "+" : "";
|
|
632
|
-
|
|
670
|
+
printLine(`Baseline: ${baseline.regressed ? "regressed" : "ok"} (${direction}${Math.round(baseline.passRateDelta * 100)} pp pass rate)`);
|
|
633
671
|
if (baseline.newFailedExamples.length > 0) {
|
|
634
672
|
cliLogger.warn(`New failed examples: ${baseline.newFailedExamples.join(", ")}`);
|
|
635
673
|
}
|
|
@@ -973,20 +1011,21 @@ export async function runEvalCommand(options, dependencies = {}) {
|
|
|
973
1011
|
}));
|
|
974
1012
|
}
|
|
975
1013
|
else {
|
|
976
|
-
for (const child of outcome.outputHints.children ?? []) {
|
|
1014
|
+
for (const [index, child] of (outcome.outputHints.children ?? []).entries()) {
|
|
1015
|
+
if (index > 0)
|
|
1016
|
+
printBlankLine();
|
|
977
1017
|
if (child.kind === "report") {
|
|
978
|
-
printReport(child.report);
|
|
979
|
-
cliLogger.info(`Report directory: ${child.reportDirectory}`);
|
|
1018
|
+
printReport(child.report, { name: child.name });
|
|
980
1019
|
}
|
|
981
1020
|
else {
|
|
982
1021
|
cliLogger.error(`Eval ${child.evalId}: ${child.error}`);
|
|
983
1022
|
}
|
|
984
1023
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1024
|
+
printBlankLine();
|
|
1025
|
+
printLine(`Eval suite: ${outcome.suite.passed}/${outcome.suite.total} passed`);
|
|
1026
|
+
printArtifact("Report", outcome.outputHints.reportMarkdown);
|
|
988
1027
|
if (outcome.outputHints.junit)
|
|
989
|
-
|
|
1028
|
+
printArtifact("JUnit", outcome.outputHints.junit);
|
|
990
1029
|
}
|
|
991
1030
|
return outcome.exitCode;
|
|
992
1031
|
});
|
|
@@ -1071,24 +1110,27 @@ export async function runEvalCommand(options, dependencies = {}) {
|
|
|
1071
1110
|
}));
|
|
1072
1111
|
}
|
|
1073
1112
|
else {
|
|
1074
|
-
for (const model of outcome.outputHints.models ?? []) {
|
|
1075
|
-
|
|
1076
|
-
|
|
1113
|
+
for (const [index, model] of (outcome.outputHints.models ?? []).entries()) {
|
|
1114
|
+
if (index > 0)
|
|
1115
|
+
printBlankLine();
|
|
1116
|
+
printHeader("Model", model.model);
|
|
1117
|
+
printReport(model.report, { name: evalItem.name });
|
|
1077
1118
|
}
|
|
1078
1119
|
const recommendation = outcome.comparison.recommendation;
|
|
1079
|
-
|
|
1120
|
+
printBlankLine();
|
|
1121
|
+
printLine(`Recommendation: ${recommendation.decision}${recommendation.model ? ` (${recommendation.model})` : ""}`);
|
|
1080
1122
|
for (const reason of recommendation.reasons) {
|
|
1081
|
-
|
|
1123
|
+
printLine(` - ${reason}`);
|
|
1082
1124
|
}
|
|
1083
|
-
|
|
1125
|
+
printBlankLine();
|
|
1084
1126
|
if (outcome.outputHints.comparisonJson) {
|
|
1085
|
-
|
|
1127
|
+
printArtifact("Comparison", outcome.outputHints.comparisonJson);
|
|
1086
1128
|
}
|
|
1087
1129
|
if (outcome.outputHints.comparisonMarkdown) {
|
|
1088
|
-
|
|
1130
|
+
printArtifact("Comparison markdown", outcome.outputHints.comparisonMarkdown);
|
|
1089
1131
|
}
|
|
1090
1132
|
if (outcome.outputHints.report) {
|
|
1091
|
-
|
|
1133
|
+
printArtifact("Report JSON", outcome.outputHints.report);
|
|
1092
1134
|
}
|
|
1093
1135
|
}
|
|
1094
1136
|
return outcome.exitCode;
|
|
@@ -1138,15 +1180,18 @@ export async function runEvalCommand(options, dependencies = {}) {
|
|
|
1138
1180
|
}));
|
|
1139
1181
|
}
|
|
1140
1182
|
else {
|
|
1141
|
-
printReport(outcome.report,
|
|
1142
|
-
|
|
1143
|
-
|
|
1183
|
+
printReport(outcome.report, {
|
|
1184
|
+
name: evalItem.name,
|
|
1185
|
+
...(outcome.baseline ? { baseline: outcome.baseline } : {}),
|
|
1186
|
+
});
|
|
1187
|
+
printBlankLine();
|
|
1188
|
+
printArtifact("Report", outcome.outputHints.reportMarkdown);
|
|
1144
1189
|
if (outcome.outputHints.report)
|
|
1145
|
-
|
|
1190
|
+
printArtifact("Report JSON", outcome.outputHints.report);
|
|
1146
1191
|
if (outcome.outputHints.junit)
|
|
1147
|
-
|
|
1192
|
+
printArtifact("JUnit", outcome.outputHints.junit);
|
|
1148
1193
|
if (outcome.outputHints.baselineWritten) {
|
|
1149
|
-
|
|
1194
|
+
printArtifact("Baseline written", outcome.outputHints.baselineWritten);
|
|
1150
1195
|
}
|
|
1151
1196
|
}
|
|
1152
1197
|
return outcome.exitCode;
|
|
@@ -25,14 +25,14 @@ export default {
|
|
|
25
25
|
"agents/assistant.ts": "import { agent } from \"veryfront/agent\";\n\nexport default agent({\n id: \"assistant\",\n name: \"Assistant\",\n description: \"Turn a rough idea into a clear next move.\",\n system:\n \"Be direct and practical. Structure complex answers clearly. Use the calculator tool for arithmetic instead of calculating mentally. Plan the calculation before calling the calculator, use the fewest calls needed, and answer immediately after you have the result. For currency splits, make rounded shares add exactly to the total and explain any remainder. Write the numbers you get back in plain text, using x and / for operators, never in LaTeX or MathJax. Use other tools when they improve accuracy, and state assumptions that affect the result.\",\n tools: { calculator: true },\n maxSteps: 20,\n suggestions: [\n {\n type: \"prompt\",\n title: \"Shape an idea\",\n prompt: \"Turn this rough idea into a focused plan with the first three steps: \",\n },\n {\n type: \"prompt\",\n title: \"Run the numbers\",\n prompt:\n \"Calculate an 18% tip on $84.50, split the total among three people, and explain the result briefly.\",\n },\n ],\n});\n",
|
|
26
26
|
"app/api/ag-ui/route.ts": "import { createAgUiHandler } from \"veryfront/agent\";\n\nexport const POST = createAgUiHandler(\"assistant\");\n",
|
|
27
27
|
"app/layout.tsx": "import \"../globals.css\";\nimport { Head } from \"veryfront/head\";\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}): React.ReactNode {\n return (\n <>\n <Head>\n <title>Assistant</title>\n <link rel=\"icon\" href=\"/favicon.svg\" type=\"image/svg+xml\" />\n </Head>\n {children}\n </>\n );\n}\n",
|
|
28
|
-
"app/markdown-renderer.tsx": "\"use client\";\n\nimport ReactMarkdown from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport type { MarkdownRendererProps } from \"veryfront/markdown\";\n\n/**\n * Rich Markdown for assistant answers.\n *\n * `veryfront/markdown` presents plain source until a renderer is installed, so\n * this component supplies one. Swap in any renderer that accepts\n * `MarkdownRendererProps` to change how answers are parsed and rendered.\n */\nexport function MarkdownRenderer({ source }: MarkdownRendererProps): React.JSX.Element {\n return <ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>;\n}\n",
|
|
28
|
+
"app/markdown-renderer.tsx": "\"use client\";\n\nimport ReactMarkdown from \"react-markdown@9.0.3\";\nimport remarkGfm from \"remark-gfm@4.0.1\";\nimport type { MarkdownRendererProps } from \"veryfront/markdown\";\n\n/**\n * Rich Markdown for assistant answers.\n *\n * `veryfront/markdown` presents plain source until a renderer is installed, so\n * this component supplies one. Swap in any renderer that accepts\n * `MarkdownRendererProps` to change how answers are parsed and rendered.\n */\nexport function MarkdownRenderer({ source }: MarkdownRendererProps): React.JSX.Element {\n return <ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>;\n}\n",
|
|
29
29
|
"app/page.tsx": "\"use client\";\n\nimport { Chat } from \"veryfront/chat\";\nimport { MarkdownRendererProvider } from \"veryfront/markdown\";\nimport { MarkdownRenderer } from \"./markdown-renderer.tsx\";\n\nexport default function ChatPage(): React.JSX.Element {\n return (\n <MarkdownRendererProvider renderer={MarkdownRenderer}>\n <Chat agentId=\"assistant\" className=\"h-screen\" />\n </MarkdownRendererProvider>\n );\n}\n",
|
|
30
|
-
"evals/assistant.eval.ts": "import { datasets, evalAgent, judges, metrics } from \"veryfront/eval\";\n\
|
|
30
|
+
"evals/assistant.eval.ts": "import { datasets, evalAgent, judges, metrics } from \"veryfront/eval\";\n\nexport default evalAgent({\n name: \"Assistant smoke test\",\n target: \"agent:assistant\",\n\n dataset: datasets.inline([\n {\n id: \"calculator\",\n input:\n \"Calculate an 18% tip on $84.50, split the total among three people, and explain the result briefly.\",\n reference:\n \"The tip is $15.21 and the total is $99.71. Two people pay $33.24 and one pays $33.23.\",\n },\n ]),\n\n metrics: [\n metrics.agent.calledTool(\"calculator\").gate(),\n\n metrics.agent.noFailedTools().gate(),\n\n metrics.judge.rubric({\n rubric: [\n \"The answer must state a tip of $15.21 and a total of $99.71.\",\n \"It must split the total into $33.24, $33.24, and $33.23.\",\n ].join(\" \"),\n judge: judges.llm.rubric(),\n }).gate({ min: 0.8 }),\n ],\n});\n",
|
|
31
31
|
"globals.css": "@import \"tailwindcss\";\n",
|
|
32
32
|
"public/favicon.svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\">\n <rect width=\"64\" height=\"64\" fill=\"#fff\"/>\n <circle cx=\"32\" cy=\"32\" r=\"20\" fill=\"#000\"/>\n</svg>\n",
|
|
33
33
|
"README.md": "# AI Agent\n\nA small, customizable agent with a streaming chat UI and tool support.\n\n## What's included\n\n- Single assistant agent with streaming chat UI\n- Example calculator tool\n- Smoke eval for the agent and calculator\n- App-mode `Chat` component for real-time responses\n\n## Structure\n\n```\nagents/assistant.ts Agent definition\ntools/calculator.ts Example tool\nevals/assistant.eval.ts Agent smoke eval\napp/\n api/ag-ui/route.ts AG-UI endpoint\n page.tsx Chat interface\n```\n\n## Customize\n\n- Edit `agents/assistant.ts` to change the agent's identity, instructions, and suggestions.\n- Add or replace files in `tools/` to give the agent new capabilities.\n- Update `evals/assistant.eval.ts`, then run `npm run eval -- assistant`.\n- Edit `app/page.tsx` when you need to customize the chat UI.\n",
|
|
34
34
|
"tools/calculator.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\n\nexport default tool({\n id: \"calculator\",\n description: \"Perform arithmetic. For round, a is the value and b is the decimal places.\",\n inputSchema: defineSchema((v) => v.object({\n operation: v.enum([\"add\", \"subtract\", \"multiply\", \"divide\", \"round\"]),\n a: v.number(),\n b: v.number(),\n }))(),\n execute: ({ operation, a, b }) => {\n const precision = Math.min(100, Math.max(0, Math.trunc(b)));\n\n if (operation === \"divide\" && b === 0) {\n throw new Error(\"Cannot divide by zero\");\n }\n\n if (operation === \"add\") return { result: a + b };\n if (operation === \"subtract\") return { result: a - b };\n if (operation === \"multiply\") return { result: a * b };\n if (operation === \"round\") {\n const offset = Math.sign(a) * Number.EPSILON * Math.max(1, Math.abs(a));\n return { result: Number((a + offset).toFixed(precision)) };\n }\n return { result: a / b };\n },\n});\n",
|
|
35
|
-
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"noEmit\": true,\n \"allowImportingTsExtensions\": true,\n \"paths\": {\n \"@/*\": [\n \"./*\"\n ]\n }\n },\n \"include\": [\n \"**/*.ts\",\n \"**/*.tsx\"\n ],\n \"exclude\": [\n \"node_modules\"\n ]\n}\n"
|
|
35
|
+
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"noEmit\": true,\n \"allowImportingTsExtensions\": true,\n \"paths\": {\n \"@/*\": [\n \"./*\"\n ],\n \"react-markdown@*\": [\n \"./node_modules/react-markdown\"\n ],\n \"remark-gfm@*\": [\n \"./node_modules/remark-gfm\"\n ]\n }\n },\n \"include\": [\n \"**/*.ts\",\n \"**/*.tsx\"\n ],\n \"exclude\": [\n \"node_modules\"\n ]\n}\n"
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"coding-agent": {
|
|
@@ -40,7 +40,7 @@ export default {
|
|
|
40
40
|
"agents/coder.ts": "import { agent } from \"veryfront/agent\";\n\nexport default agent({\n id: \"coder\",\n name: \"Code Agent\",\n description: \"Read, search, and edit project files.\",\n system: `You are an expert coding assistant. You can read, search, and modify code files in the project.\n\nWhen asked to make changes:\n1. First read the relevant files to understand the codebase\n2. Explain what you'll change and why\n3. Make the changes\n4. Verify the result\n\nAlways explain your reasoning before making edits.`,\n tools: true,\n maxSteps: 15,\n suggestions: [\n {\n type: \"prompt\",\n title: \"Explain the codebase\",\n prompt: \"Explain how this project is organized.\",\n },\n {\n type: \"prompt\",\n title: \"Make a change\",\n prompt: \"Make this code change: \",\n },\n ],\n});\n",
|
|
41
41
|
"app/api/ag-ui/route.ts": "import { createAgUiHandler } from \"veryfront/agent\";\n\nexport const POST = createAgUiHandler(\"coder\");\n",
|
|
42
42
|
"app/layout.tsx": "import \"../globals.css\";\nimport { Head } from \"veryfront/head\";\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}): React.ReactNode {\n return (\n <>\n <Head>\n <title>Code Agent</title>\n <link rel=\"icon\" href=\"/favicon.svg\" type=\"image/svg+xml\" />\n </Head>\n <div className=\"dark\">\n <div className=\"flex flex-col h-screen bg-neutral-950\">\n <header className=\"flex-shrink-0 border-b border-neutral-800\">\n <div className=\"max-w-4xl mx-auto flex items-center gap-3 px-4 py-3\">\n <div className=\"w-8 h-8 rounded-lg bg-emerald-500/10 flex items-center justify-center\">\n <svg\n className=\"w-4 h-4 text-emerald-400\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5\"\n />\n </svg>\n </div>\n <div>\n <h1 className=\"font-medium text-white text-sm font-mono\">\n code-agent\n </h1>\n <p className=\"text-xs text-neutral-500\">\n read, search, edit project files\n </p>\n </div>\n <div className=\"ml-auto flex items-center gap-1.5\">\n <span className=\"w-2 h-2 rounded-full bg-emerald-400 animate-pulse\" />\n <span className=\"text-xs text-neutral-500 font-mono\">ready</span>\n </div>\n </div>\n </header>\n {children}\n </div>\n </div>\n </>\n );\n}\n",
|
|
43
|
-
"app/markdown-renderer.tsx": "'use client'\n\nimport ReactMarkdown from 'react-markdown'\nimport remarkGfm from 'remark-gfm'\nimport type { MarkdownRendererProps } from 'veryfront/markdown'\n\n/**\n * Rich Markdown for assistant answers.\n *\n * `veryfront/markdown` presents plain source until a renderer is installed, so\n * this component supplies one. Swap in any renderer that accepts\n * `MarkdownRendererProps` to change how answers are parsed and rendered.\n */\nexport function MarkdownRenderer({ source }: MarkdownRendererProps): React.JSX.Element {\n return <ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>\n}\n",
|
|
43
|
+
"app/markdown-renderer.tsx": "'use client'\n\nimport ReactMarkdown from 'react-markdown@9.0.3'\nimport remarkGfm from 'remark-gfm@4.0.1'\nimport type { MarkdownRendererProps } from 'veryfront/markdown'\n\n/**\n * Rich Markdown for assistant answers.\n *\n * `veryfront/markdown` presents plain source until a renderer is installed, so\n * this component supplies one. Swap in any renderer that accepts\n * `MarkdownRendererProps` to change how answers are parsed and rendered.\n */\nexport function MarkdownRenderer({ source }: MarkdownRendererProps): React.JSX.Element {\n return <ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>\n}\n",
|
|
44
44
|
"app/page.tsx": "'use client'\n\nimport { Chat } from 'veryfront/chat'\nimport { MarkdownRendererProvider } from 'veryfront/markdown'\nimport { MarkdownRenderer } from './markdown-renderer.tsx'\n\nexport default function CodeAgent(): React.JSX.Element {\n return (\n <MarkdownRendererProvider renderer={MarkdownRenderer}>\n <Chat\n agentId=\"coder\"\n api=\"/api/ag-ui\"\n className=\"flex-1 min-h-0\"\n placeholder=\"Describe what you want to build or fix...\"\n />\n </MarkdownRendererProvider>\n )\n}\n",
|
|
45
45
|
"globals.css": "@import \"tailwindcss\";\n",
|
|
46
46
|
"public/favicon.svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\">\n <rect width=\"64\" height=\"64\" fill=\"#fff\"/>\n <circle cx=\"32\" cy=\"32\" r=\"20\" fill=\"#000\"/>\n</svg>\n",
|
|
@@ -48,7 +48,7 @@ export default {
|
|
|
48
48
|
"tools/edit-file.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { cwd, readTextFile, realPath, resolve, writeTextFile } from \"veryfront/fs\";\n\n/** True when `target` is the same as, or nested under, `root` (both canonical). */\nfunction isWithin(root: string, target: string): boolean {\n const r = root.replace(/\\\\/g, \"/\");\n const t = target.replace(/\\\\/g, \"/\");\n return t === r || t.startsWith(`${r}/`);\n}\n\nexport default tool({\n id: \"edit-file\",\n description: \"Edit a file by replacing a specific string with new content\",\n inputSchema: defineSchema((v) => v.object({\n path: v.string().describe(\"File path relative to the project root\"),\n search: v.string().describe(\"Exact string to find in the file\"),\n replace: v.string().describe(\"String to replace it with\"),\n }))(),\n execute: async ({ path, search, replace }) => {\n let projectDir: string;\n let absolute: string;\n try {\n // Canonicalize both sides so a symlink that points outside the project\n // is resolved to its real target before the containment check.\n projectDir = await realPath(cwd());\n absolute = await realPath(resolve(projectDir, path));\n } catch {\n return { error: `File not found: ${path}` };\n }\n if (!isWithin(projectDir, absolute)) {\n return { error: `Path escapes project directory: ${path}` };\n }\n\n const content = await readTextFile(absolute);\n if (!content.includes(search)) {\n return { error: \"Search string not found in file\" };\n }\n\n const updated = content.replace(search, replace);\n await writeTextFile(absolute, updated);\n return { path, success: true };\n },\n});\n",
|
|
49
49
|
"tools/list-files.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { cwd, readDir, realPath, resolve } from \"veryfront/fs\";\n\n/** True when `target` is the same as, or nested under, `root` (both canonical). */\nfunction isWithin(root: string, target: string): boolean {\n const r = root.replace(/\\\\/g, \"/\");\n const t = target.replace(/\\\\/g, \"/\");\n return t === r || t.startsWith(`${r}/`);\n}\n\nexport default tool({\n id: \"list-files\",\n description: \"List files in a project directory\",\n inputSchema: defineSchema((v) => v.object({\n directory: v\n .string()\n .default(\".\")\n .describe(\"Directory path relative to project root\"),\n extensions: v\n .array(v.string())\n .optional()\n .describe(\"Filter by file extensions (e.g. ['.ts', '.tsx'])\"),\n }))(),\n execute: async ({ directory, extensions }) => {\n let projectDir: string;\n let absolute: string;\n try {\n // Canonicalize both sides so a symlink that points outside the project\n // is resolved to its real target before the containment check.\n projectDir = await realPath(cwd());\n absolute = await realPath(resolve(projectDir, directory));\n } catch {\n return { error: `Directory not found: ${directory}` };\n }\n if (!isWithin(projectDir, absolute)) {\n return { error: `Path escapes project directory: ${directory}` };\n }\n\n const entries = await readDir(absolute);\n\n let files = entries\n .filter((e) => e.isFile)\n .map((e) => e.name);\n\n if (extensions?.length) {\n files = files.filter((f) =>\n extensions.some((ext) => f.endsWith(ext))\n );\n }\n\n return { directory, files, count: files.length };\n },\n});\n",
|
|
50
50
|
"tools/read-file.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\nimport { cwd, readTextFile, realPath, resolve } from \"veryfront/fs\";\n\n/** True when `target` is the same as, or nested under, `root` (both canonical). */\nfunction isWithin(root: string, target: string): boolean {\n const r = root.replace(/\\\\/g, \"/\");\n const t = target.replace(/\\\\/g, \"/\");\n return t === r || t.startsWith(`${r}/`);\n}\n\nexport default tool({\n id: \"read-file\",\n description: \"Read the contents of a file in the project\",\n inputSchema: defineSchema((v) => v.object({\n path: v.string().describe(\"File path relative to the project root\"),\n }))(),\n execute: async ({ path }) => {\n let projectDir: string;\n let absolute: string;\n try {\n // Canonicalize both sides so a symlink that points outside the project\n // is resolved to its real target before the containment check.\n projectDir = await realPath(cwd());\n absolute = await realPath(resolve(projectDir, path));\n } catch {\n return { error: `File not found: ${path}` };\n }\n if (!isWithin(projectDir, absolute)) {\n return { error: `Path escapes project directory: ${path}` };\n }\n const content = await readTextFile(absolute);\n return { path, content };\n },\n});\n",
|
|
51
|
-
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"noEmit\": true,\n \"allowImportingTsExtensions\": true,\n \"paths\": {\n \"@/*\": [\n \"./*\"\n ]\n }\n },\n \"include\": [\n \"**/*.ts\",\n \"**/*.tsx\"\n ],\n \"exclude\": [\n \"node_modules\"\n ]\n}\n"
|
|
51
|
+
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"noEmit\": true,\n \"allowImportingTsExtensions\": true,\n \"paths\": {\n \"@/*\": [\n \"./*\"\n ],\n \"react-markdown@*\": [\n \"./node_modules/react-markdown\"\n ],\n \"remark-gfm@*\": [\n \"./node_modules/remark-gfm\"\n ]\n }\n },\n \"include\": [\n \"**/*.ts\",\n \"**/*.tsx\"\n ],\n \"exclude\": [\n \"node_modules\"\n ]\n}\n"
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"docs-agent": {
|
|
@@ -58,7 +58,7 @@ export default {
|
|
|
58
58
|
"app/api/ingest/route.ts": "import { store } from \"../../../store.ts\";\n\nexport async function POST() {\n await store.indexContentDir();\n return Response.json({ ok: true });\n}\n",
|
|
59
59
|
"app/api/uploads/route.ts": "import { createUploadHandler } from \"veryfront/embedding\";\nimport { store } from \"../../../store.ts\";\n\nexport const { POST, GET, DELETE } = createUploadHandler(store, {\n auth: { type: \"none\", allowUnauthenticated: true },\n});\n",
|
|
60
60
|
"app/layout.tsx": "'use client'\n\nimport \"../globals.css\";\nimport {\n AppShell,\n ChatSidebar,\n ChatThemeScope,\n ConversationsProvider,\n Tabs,\n TabsItem,\n} from \"veryfront/chat\";\nimport { Head } from \"veryfront/head\";\nimport { useRouter } from \"veryfront/router\";\n\nexport default function RootLayout({ children }: { children: React.ReactNode }): React.ReactNode {\n const router = useRouter();\n const activeTab = router.pathname.startsWith(\"/uploads\") ? \"uploads\" : \"chat\";\n\n return (\n <>\n <Head>\n <title>Docs Agent</title>\n <link rel=\"icon\" href=\"/favicon.svg\" type=\"image/svg+xml\" />\n </Head>\n <ChatThemeScope className=\"flex flex-col h-screen\">\n <ConversationsProvider storageKey=\"rag-conversations\">\n <AppShell className=\"flex-1 min-h-0\">\n <AppShell.Sidebar side=\"left\" className=\"border-r border-[var(--outline-border)]\">\n <AppShell.SidebarContent className=\"p-0\">\n <ChatSidebar.Root>\n <ChatSidebar.NewButton />\n <ChatSidebar.List />\n </ChatSidebar.Root>\n </AppShell.SidebarContent>\n </AppShell.Sidebar>\n <AppShell.Main>\n <AppShell.Header border className=\"h-16 gap-3 px-3\">\n <AppShell.Trigger side=\"left\" />\n <div className=\"flex flex-1 justify-center\">\n <Tabs\n value={activeTab}\n onValueChange={(value: string) =>\n router.push(value === \"uploads\" ? \"/uploads\" : \"/\")}\n >\n <TabsItem value=\"chat\">Chat</TabsItem>\n <TabsItem value=\"uploads\">Uploads</TabsItem>\n </Tabs>\n </div>\n </AppShell.Header>\n <AppShell.Content className=\"flex flex-col min-h-0 pt-3\">\n {children}\n </AppShell.Content>\n </AppShell.Main>\n </AppShell>\n </ConversationsProvider>\n </ChatThemeScope>\n </>\n );\n}\n",
|
|
61
|
-
"app/markdown-renderer.tsx": "'use client'\n\nimport ReactMarkdown from 'react-markdown'\nimport remarkGfm from 'remark-gfm'\nimport type { MarkdownRendererProps } from 'veryfront/markdown'\n\n/**\n * Rich Markdown for assistant answers.\n *\n * `veryfront/markdown` presents plain source until a renderer is installed, so\n * this component supplies one. Swap in any renderer that accepts\n * `MarkdownRendererProps` to change how answers are parsed and rendered.\n */\nexport function MarkdownRenderer({ source }: MarkdownRendererProps): React.JSX.Element {\n return <ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>\n}\n",
|
|
61
|
+
"app/markdown-renderer.tsx": "'use client'\n\nimport ReactMarkdown from 'react-markdown@9.0.3'\nimport remarkGfm from 'remark-gfm@4.0.1'\nimport type { MarkdownRendererProps } from 'veryfront/markdown'\n\n/**\n * Rich Markdown for assistant answers.\n *\n * `veryfront/markdown` presents plain source until a renderer is installed, so\n * this component supplies one. Swap in any renderer that accepts\n * `MarkdownRendererProps` to change how answers are parsed and rendered.\n */\nexport function MarkdownRenderer({ source }: MarkdownRendererProps): React.JSX.Element {\n return <ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>\n}\n",
|
|
62
62
|
"app/page.tsx": "'use client'\n\nimport { Chat } from 'veryfront/chat'\nimport { MarkdownRendererProvider } from 'veryfront/markdown'\nimport { MarkdownRenderer } from './markdown-renderer.tsx'\n\nexport default function DocsChat(): React.JSX.Element {\n return (\n <MarkdownRendererProvider renderer={MarkdownRenderer}>\n <Chat\n agentId=\"rag\"\n api=\"/api/ag-ui\"\n className=\"flex-1 min-h-0\"\n placeholder=\"Ask anything about your documents...\"\n />\n </MarkdownRendererProvider>\n )\n}\n",
|
|
63
63
|
"app/uploads/page.tsx": "'use client'\n\nimport { AttachmentsPanel, useUploadsRegistry } from 'veryfront/chat'\n\nconst UPLOAD_API = '/api/uploads'\nconst ACCEPT = '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.csv,.txt,.md,.mdx,.html,.rtf,.epub,.json,.xml'\n\nexport default function UploadsPage(): React.JSX.Element {\n const uploads = useUploadsRegistry({ url: UPLOAD_API, storageKey: 'rag-uploads' })\n const error = uploads.uploadError\n ?? uploads.refreshError\n ?? uploads.removeError\n ?? uploads.storageError\n\n return (\n <>\n {error && (\n <p role=\"alert\" className=\"m-4 rounded-md bg-red-50 p-3 text-sm text-red-800\">\n {error.message}\n </p>\n )}\n <AttachmentsPanel\n uploads={uploads.items}\n loading={uploads.isLoading}\n onAttach={uploads.upload}\n onRemoveUpload={uploads.remove}\n attachAccept={ACCEPT}\n className=\"flex-1 min-h-0\"\n />\n </>\n )\n}\n",
|
|
64
64
|
"content/architecture.md": "# Architecture\n\nAcme Platform uses a modular, event-driven architecture.\n\n## Core Components\n\n### API Gateway\nRoutes incoming requests to the appropriate microservice. Handles authentication, rate limiting, and request validation.\n\n### Event Bus\nAsynchronous message broker connecting all services. Supports pub/sub and point-to-point messaging patterns.\n\n### Data Layer\nMulti-tenant data storage with automatic sharding. Supports PostgreSQL for relational data and Redis for caching.\n\n## Request Flow\n\n1. Client sends request to API Gateway\n2. Gateway validates authentication token\n3. Request is routed to the target service\n4. Service processes request and publishes events\n5. Response is returned through the gateway\n\n## Scaling\n\nEach component scales independently. The API Gateway uses horizontal scaling with load balancing. Services auto-scale based on queue depth and CPU utilization.\n\n## Security\n\n- All inter-service communication uses mTLS\n- API tokens are rotated every 24 hours\n- Data at rest is encrypted with AES-256\n- Audit logs are retained for 90 days\n",
|
|
@@ -68,7 +68,7 @@ export default {
|
|
|
68
68
|
"public/favicon.svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\">\n <rect width=\"64\" height=\"64\" fill=\"#fff\"/>\n <circle cx=\"32\" cy=\"32\" r=\"20\" fill=\"#000\"/>\n</svg>\n",
|
|
69
69
|
"README.md": "# Docs Agent\n\nA chatbot that answers questions from your own documents using Retrieval-Augmented Generation (RAG).\n\n## What's included\n\n- Q&A agent with source citation\n- Embedding-based semantic search with convention-based model selection\n- Document upload supporting PDF, DOCX, XLSX, PPTX, CSV, HTML, RTF, EPUB, TXT, and Markdown\n- `ragStore()` with local JSON storage by default, and Veryfront Cloud RAG when bootstrap is present\n- Veryfront Cloud AI Gateway routing for generation and embeddings when bootstrap is present\n- Original uploaded files stored in Veryfront Cloud project uploads when cloud bootstrap is present\n- Sample content in `/content` indexed through a dedicated ingestion route\n\n## Getting started\n\n1. Set your Veryfront Cloud bootstrap vars:\n\n ```bash\n export VERYFRONT_API_TOKEN=vf_...\n export VERYFRONT_PROJECT_SLUG=my-project\n ```\n\n2. Start the dev server:\n\n ```bash\n npx veryfront dev\n ```\n\n3. Index the sample docs in `content/`:\n\n ```bash\n curl -X POST http://localhost:3000/api/ingest\n ```\n\n4. Open the app and upload a document or ask a question.\n\nIf you are using a self-hosted Veryfront API, also set `VERYFRONT_API_URL`.\n\n## Cloud mode\n\nWith `VERYFRONT_API_TOKEN` and `VERYFRONT_PROJECT_SLUG` set, the template uses\nVeryfront Cloud automatically:\n\n- Agent generation routes through Veryfront Cloud model routing.\n- Query and document embeddings route through Veryfront Cloud embedding routing.\n- `veryfront-cloud/openai/...` and `veryfront-cloud/google/...` models use AI Gateway.\n- RAG documents, chunks, and embeddings are stored in the target project.\n\nThe default cloud embedding model is\n`veryfront-cloud/openai/text-embedding-3-small`. Set\n`VERYFRONT_DEFAULT_EMBEDDING_MODEL` only when you need a different embedding\nmodel.\n\n## Architecture\n\nRAG grounds LLM responses in your documents through three pipelines: **Ingestion**, **Query**, and **RAG**. These pipelines are orchestrated around a shared vector store.\n\n```mermaid\nflowchart LR\n ChatUI_L[\"Chat UI\"]\n\n subgraph IngestionFlow[\"Ingestion Pipeline\"]\n D[\"Documents\"] --> EXT[\"Extraction\"] --> DC[\"Chunking\"] --> DE[\"Document\\nEmbedding\"] --> ING[\"Storage\"]\n end\n\n subgraph QueryFlow[\"Query Pipeline\"]\n Q[\"Query\"] --> QE[\"Query\\nEmbedding\"] --> SS[\"Similarity\\nSearch\"]\n end\n\n subgraph RAGFlow[\"RAG Pipeline\"]\n BF[\"beforeStream\\nHook\"] --> RET[\"Retrieval\"] --> AUG[\"Augmentation\"] --> AG[\"Agent\"] --> GEN[\"Generation\"]\n end\n\n EMB((\"Embedding\\nModel\"))\n GEN_LLM((\"Generative\\nModel\"))\n VS[(\"Vector\\nStore\")]\n ChatUI_R[\"Chat UI\"]\n\n ChatUI_L --> D\n ChatUI_L --> Q\n\n QE -.- EMB\n DE -.- EMB\n\n SS --> VS\n ING --> VS\n\n Q --> BF\n VS --> RET\n AG -.- GEN_LLM\n GEN -.- GEN_LLM\n GEN --> ChatUI_R\n```\n\n### Pipelines\n\n**Ingestion**: Documents are parsed into plain text via the built-in kreuzberg extraction engine (supporting PDF, DOCX, XLSX, PPTX, HTML, RTF, EPUB, and 76+ formats), split into overlapping chunks (~1000 chars, 200 char overlap), and stored in the default `ragStore()`. In local mode that means `data/index.json`; with Veryfront Cloud bootstrap it upgrades to the cloud RAG backend automatically. The original uploaded binary is also stored in the project's Veryfront Cloud uploads store so remote projects retain the source file, not just the extracted text. Uploaded files are ingested by the upload route. Bundled files in `content/` are ingested by the `/api/ingest` route.\n\n**Query**: The user's query is embedded into the same vector space as the documents, then compared against all stored chunks using cosine similarity to find the top-*k* most relevant results.\n\n**RAG**: The `beforeStream` hook in the AG-UI route intercepts each message before it reaches the agent. It searches the document store for relevant chunks, assembles them into context, and prepends them as retrieved reference data. The agent then generates a cited response streamed back to the user.\n\n## Structure\n\n```\nstore.ts RAG store config\nagents/rag.ts Q&A agent with citation instructions\ncontent/\n getting-started.md Sample document\n architecture.md Sample document\napp/\n api/ag-ui/route.ts AG-UI endpoint\n api/ingest/route.ts Bundled content ingestion\n api/uploads/route.ts Upload, list, and delete uploads\n uploads/page.tsx Upload management panel\n page.tsx Chat UI\n layout.tsx Chat shell with sidebar and tabs\n```\n\n## Framework usage\n\n| What | Framework | Template code |\n|------|-----------|---------------|\n| Chat UI + streaming | app-mode `Chat` | `page.tsx` |\n| Upload management | `AttachmentsPanel`, `useUploadsRegistry` | `uploads/page.tsx` |\n| Source display | Built into the `Chat` preset | `page.tsx` |\n| Upload API route | `createUploadHandler` | 1 line in `route.ts` |\n| AG-UI route | `createAgUiHandler` | 1 line in `route.ts` |\n| Agent definition | `agent()` | Config object in `agents/rag.ts` |\n| RAG retrieval | `beforeStream` hook | Context injection in `api/ag-ui/route.ts` |\n| Vector store | `ragStore()` | Config in `store.ts` |\n\n## Adding documents\n\n- Drop files into `content/`, then run `curl -X POST http://localhost:3000/api/ingest`\n- Or use the upload panel in the UI for PDF, DOCX, XLSX, PPTX, CSV, HTML, RTF, EPUB, TXT, and MD files\n\n## Production notes\n\nThis is a starter template, not a production-ready setup. For production, consider:\n\n- **Vector store**: Replace the default store with pgvector, Pinecone, or Qdrant for datasets beyond ~10k chunks\n- **Reranking**: Add a cross-encoder reranker (e.g. Cohere Rerank) after retrieval to improve precision\n- **Hybrid search**: Combine dense vectors with BM25 keyword matching for better recall\n",
|
|
70
70
|
"store.ts": "import { ragStore } from \"veryfront/embedding\";\n\nexport const store = ragStore({\n storagePath: \"data/index.json\",\n contentDir: \"content\",\n});\n",
|
|
71
|
-
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"noEmit\": true,\n \"allowImportingTsExtensions\": true,\n \"paths\": {\n \"@/*\": [\"./*\"]\n }\n },\n \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n \"exclude\": [\"node_modules\"]\n}\n"
|
|
71
|
+
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"noEmit\": true,\n \"allowImportingTsExtensions\": true,\n \"paths\": {\n \"@/*\": [\"./*\"],\n \"react-markdown@*\": [\"./node_modules/react-markdown\"],\n \"remark-gfm@*\": [\"./node_modules/remark-gfm\"]\n }\n },\n \"include\": [\"**/*.ts\", \"**/*.tsx\"],\n \"exclude\": [\"node_modules\"]\n}\n"
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
74
|
"minimal": {
|
|
@@ -87,13 +87,13 @@ export default {
|
|
|
87
87
|
"agents/writer.ts": "import { agent } from \"veryfront/agent\";\n\nexport default agent({\n id: \"writer\",\n system:\n \"You are a writing specialist. \" +\n \"Take research notes and transform them into clear, engaging prose. \" +\n \"Use a professional but approachable tone.\",\n maxSteps: 3,\n});\n",
|
|
88
88
|
"app/api/ag-ui/route.ts": "import { createAgUiHandler } from \"veryfront/agent\";\n\nexport const POST = createAgUiHandler(\"orchestrator\");\n",
|
|
89
89
|
"app/layout.tsx": "import \"../globals.css\";\nimport { Head } from \"veryfront/head\";\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}): React.ReactNode {\n return (\n <>\n <Head>\n <title>Multi-Agent System</title>\n <link rel=\"icon\" href=\"/favicon.svg\" type=\"image/svg+xml\" />\n </Head>\n <div className=\"flex flex-col h-screen bg-white dark:bg-neutral-950\">\n <header className=\"flex-shrink-0 border-b border-neutral-200 dark:border-neutral-800\">\n <div className=\"max-w-3xl mx-auto flex items-center gap-3 px-4 py-3\">\n <div className=\"flex -space-x-2\">\n <div className=\"w-7 h-7 rounded-full bg-blue-500 ring-2 ring-white dark:ring-neutral-950 flex items-center justify-center text-[10px] font-bold text-white\">\n O\n </div>\n <div className=\"w-7 h-7 rounded-full bg-amber-500 ring-2 ring-white dark:ring-neutral-950 flex items-center justify-center text-[10px] font-bold text-white\">\n R\n </div>\n <div className=\"w-7 h-7 rounded-full bg-violet-500 ring-2 ring-white dark:ring-neutral-950 flex items-center justify-center text-[10px] font-bold text-white\">\n W\n </div>\n </div>\n <div>\n <h1 className=\"font-medium text-neutral-900 dark:text-white text-sm\">\n Agent Team\n </h1>\n <p className=\"text-xs text-neutral-500 dark:text-neutral-400\">\n Orchestrator, Researcher, Writer\n </p>\n </div>\n </div>\n </header>\n {children}\n </div>\n </>\n );\n}\n",
|
|
90
|
-
"app/markdown-renderer.tsx": "'use client'\n\nimport ReactMarkdown from 'react-markdown'\nimport remarkGfm from 'remark-gfm'\nimport type { MarkdownRendererProps } from 'veryfront/markdown'\n\n/**\n * Rich Markdown for assistant answers.\n *\n * `veryfront/markdown` presents plain source until a renderer is installed, so\n * this component supplies one. Swap in any renderer that accepts\n * `MarkdownRendererProps` to change how answers are parsed and rendered.\n */\nexport function MarkdownRenderer({ source }: MarkdownRendererProps): React.JSX.Element {\n return <ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>\n}\n",
|
|
90
|
+
"app/markdown-renderer.tsx": "'use client'\n\nimport ReactMarkdown from 'react-markdown@9.0.3'\nimport remarkGfm from 'remark-gfm@4.0.1'\nimport type { MarkdownRendererProps } from 'veryfront/markdown'\n\n/**\n * Rich Markdown for assistant answers.\n *\n * `veryfront/markdown` presents plain source until a renderer is installed, so\n * this component supplies one. Swap in any renderer that accepts\n * `MarkdownRendererProps` to change how answers are parsed and rendered.\n */\nexport function MarkdownRenderer({ source }: MarkdownRendererProps): React.JSX.Element {\n return <ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>\n}\n",
|
|
91
91
|
"app/page.tsx": "'use client'\n\nimport { Chat } from 'veryfront/chat'\nimport { MarkdownRendererProvider } from 'veryfront/markdown'\nimport { MarkdownRenderer } from './markdown-renderer.tsx'\n\nexport default function MultiAgentChat(): React.JSX.Element {\n return (\n <MarkdownRendererProvider renderer={MarkdownRenderer}>\n <Chat\n agentId=\"orchestrator\"\n api=\"/api/ag-ui\"\n className=\"flex-1 min-h-0\"\n placeholder=\"Give the team a task...\"\n />\n </MarkdownRendererProvider>\n )\n}\n",
|
|
92
92
|
"globals.css": "@import \"tailwindcss\";\n",
|
|
93
93
|
"public/favicon.svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\">\n <rect width=\"64\" height=\"64\" fill=\"#fff\"/>\n <circle cx=\"32\" cy=\"32\" r=\"20\" fill=\"#000\"/>\n</svg>\n",
|
|
94
94
|
"README.md": "# Multi-Agent System\n\nA team of specialized agents that collaborate on tasks.\n\n## What's included\n\n- Orchestrator that delegates to researcher and writer agents\n- Agent-as-tool composition via `getAgentsAsTools()`\n- Web search tool (placeholder, configure your own API)\n\n## Structure\n\n```\nagents/\n orchestrator.ts Coordinates the team\n researcher.ts Gathers information\n writer.ts Produces polished content\ntools/web-search.ts Placeholder search tool\napp/\n api/ag-ui/route.ts AG-UI endpoint\n page.tsx Chat interface\n```\n\nThis starter is not production-ready.\n",
|
|
95
95
|
"tools/web-search.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\n\nexport default tool({\n id: \"web-search\",\n description: \"Search the web for information on a topic\",\n inputSchema: defineSchema((v) => v.object({\n query: v.string().describe(\"Search query\"),\n }))(),\n execute: async ({ query: _query }) => {\n // Connect a real search API to use this tool.\n // Popular options: Tavily, SerpAPI, Brave Search\n throw new Error(\n \"No search API configured. \" +\n \"See https://veryfront.com/docs/code/guides/tools for setup instructions.\",\n );\n },\n});\n",
|
|
96
|
-
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"noEmit\": true,\n \"allowImportingTsExtensions\": true,\n \"paths\": {\n \"@/*\": [\n \"./*\"\n ]\n }\n },\n \"include\": [\n \"**/*.ts\",\n \"**/*.tsx\"\n ],\n \"exclude\": [\n \"node_modules\"\n ]\n}\n"
|
|
96
|
+
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"noEmit\": true,\n \"allowImportingTsExtensions\": true,\n \"paths\": {\n \"@/*\": [\n \"./*\"\n ],\n \"react-markdown@*\": [\n \"./node_modules/react-markdown\"\n ],\n \"remark-gfm@*\": [\n \"./node_modules/remark-gfm\"\n ]\n }\n },\n \"include\": [\n \"**/*.ts\",\n \"**/*.tsx\"\n ],\n \"exclude\": [\n \"node_modules\"\n ]\n}\n"
|
|
97
97
|
}
|
|
98
98
|
},
|
|
99
99
|
"saas-starter": {
|
|
@@ -103,13 +103,13 @@ export default {
|
|
|
103
103
|
"app/dashboard/page.tsx": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Chat } from \"veryfront/chat\";\nimport { MarkdownRendererProvider } from \"veryfront/markdown\";\nimport { MarkdownRenderer } from \"../markdown-renderer.tsx\";\n\ninterface Conversation {\n id: string;\n title: string;\n updatedAt: string;\n}\n\nconst INITIAL_CONVERSATIONS: Conversation[] = [\n { id: \"1\", title: \"Getting started\", updatedAt: \"Just now\" },\n];\n\nexport default function Dashboard(): React.JSX.Element {\n const [conversations] = useState<Conversation[]>(INITIAL_CONVERSATIONS);\n const [activeId, setActiveId] = useState(\"1\");\n\n return (\n <div className=\"flex h-screen bg-white dark:bg-neutral-950\">\n {/* Sidebar */}\n <aside className=\"w-64 border-r border-neutral-200 dark:border-neutral-800 flex flex-col bg-neutral-50 dark:bg-neutral-900\">\n <div className=\"p-4 border-b border-neutral-200 dark:border-neutral-800\">\n <button className=\"w-full flex items-center gap-2 px-3 py-2 text-sm font-medium text-neutral-700 dark:text-neutral-300 bg-white dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg hover:bg-neutral-50 dark:hover:bg-neutral-700 transition-colors\">\n <svg\n className=\"w-4 h-4\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n strokeWidth={2}\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n d=\"M12 4.5v15m7.5-7.5h-15\"\n />\n </svg>\n New chat\n </button>\n </div>\n\n <nav className=\"flex-1 overflow-y-auto p-2 space-y-0.5\">\n {conversations.map((conv) => (\n <button\n key={conv.id}\n onClick={() => setActiveId(conv.id)}\n className={`w-full text-left px-3 py-2 rounded-lg text-sm transition-colors ${\n activeId === conv.id\n ? \"bg-neutral-200 dark:bg-neutral-800 text-neutral-900 dark:text-white\"\n : \"text-neutral-600 dark:text-neutral-400 hover:bg-neutral-100 dark:hover:bg-neutral-800/50\"\n }`}\n >\n <p className=\"truncate\">{conv.title}</p>\n <p className=\"text-xs text-neutral-400 mt-0.5\">\n {conv.updatedAt}\n </p>\n </button>\n ))}\n </nav>\n\n <div className=\"p-4 border-t border-neutral-200 dark:border-neutral-800\">\n <div className=\"flex items-center gap-2\">\n <div className=\"w-8 h-8 rounded-full bg-neutral-200 dark:bg-neutral-700 flex items-center justify-center text-xs font-medium text-neutral-600 dark:text-neutral-300\">\n U\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-neutral-900 dark:text-white truncate\">\n User\n </p>\n <p className=\"text-xs text-neutral-500 truncate\">\n user@example.com\n </p>\n </div>\n </div>\n </div>\n </aside>\n\n {/* Chat */}\n <main className=\"flex-1 flex flex-col\">\n <MarkdownRendererProvider renderer={MarkdownRenderer}>\n <Chat\n agentId=\"assistant\"\n api=\"/api/ag-ui\"\n className=\"flex-1 min-h-0\"\n placeholder=\"Message...\"\n />\n </MarkdownRendererProvider>\n </main>\n </div>\n );\n}\n",
|
|
104
104
|
"app/layout.tsx": "import \"../globals.css\";\nimport { Head } from \"veryfront/head\";\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}): React.ReactNode {\n return (\n <>\n <Head>\n <title>AI SaaS</title>\n <link rel=\"icon\" href=\"/favicon.svg\" type=\"image/svg+xml\" />\n </Head>\n <div className=\"antialiased\">\n {children}\n </div>\n </>\n );\n}\n",
|
|
105
105
|
"app/login/page.tsx": "\"use client\";\n\n// Demo sign-in: the buttons below pass straight through to /dashboard so the\n// starter is usable out of the box. To wire up real OAuth, scaffold provider\n// routes at app/api/auth/google/route.ts and app/api/auth/github/route.ts and\n// point the hrefs there. See https://veryfront.com/docs/code/guides/oauth.\nexport default function LoginPage(): React.JSX.Element {\n return (\n <div className=\"min-h-screen flex items-center justify-center bg-neutral-50 dark:bg-neutral-950 px-4\">\n <div className=\"w-full max-w-sm\">\n <div className=\"text-center mb-8\">\n <h1 className=\"text-xl font-bold text-neutral-900 dark:text-white\">\n Welcome back\n </h1>\n <p className=\"text-sm text-neutral-500 dark:text-neutral-400 mt-1\">\n Sign in to continue\n </p>\n </div>\n\n <div className=\"bg-white dark:bg-neutral-900 rounded-2xl border border-neutral-200 dark:border-neutral-800 p-6 space-y-3\">\n <a\n href=\"/dashboard\"\n className=\"flex items-center justify-center gap-2 w-full px-4 py-2.5 border border-neutral-200 dark:border-neutral-800 rounded-xl text-sm font-medium text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-800 transition-colors\"\n >\n <svg className=\"w-4 h-4\" viewBox=\"0 0 24 24\">\n <path\n fill=\"#4285F4\"\n d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.76h3.57c2.08-1.92 3.28-4.74 3.28-8.09z\"\n />\n <path\n fill=\"#34A853\"\n d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\"\n />\n <path\n fill=\"#FBBC05\"\n d=\"M5.84 14.09a7.12 7.12 0 010-4.18V7.07H2.18A11.99 11.99 0 001 12c0 1.94.46 3.77 1.18 5.43l3.66-3.34z\"\n />\n <path\n fill=\"#EA4335\"\n d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\"\n />\n </svg>\n Continue with Google\n </a>\n <a\n href=\"/dashboard\"\n className=\"flex items-center justify-center gap-2 w-full px-4 py-2.5 border border-neutral-200 dark:border-neutral-800 rounded-xl text-sm font-medium text-neutral-700 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-800 transition-colors\"\n >\n <svg className=\"w-4 h-4\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n <path d=\"M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.51 11.51 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z\" />\n </svg>\n Continue with GitHub\n </a>\n </div>\n\n <p className=\"mt-6 text-center text-xs text-neutral-400\">\n <a\n href=\"/\"\n className=\"hover:text-neutral-600 dark:hover:text-neutral-300\"\n >\n ← Back to home\n </a>\n </p>\n </div>\n </div>\n );\n}\n",
|
|
106
|
-
"app/markdown-renderer.tsx": "\"use client\";\n\nimport ReactMarkdown from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport type { MarkdownRendererProps } from \"veryfront/markdown\";\n\n/**\n * Rich Markdown for assistant answers.\n *\n * `veryfront/markdown` presents plain source until a renderer is installed, so\n * this component supplies one. Swap in any renderer that accepts\n * `MarkdownRendererProps` to change how answers are parsed and rendered.\n */\nexport function MarkdownRenderer({ source }: MarkdownRendererProps): React.JSX.Element {\n return <ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>;\n}\n",
|
|
106
|
+
"app/markdown-renderer.tsx": "\"use client\";\n\nimport ReactMarkdown from \"react-markdown@9.0.3\";\nimport remarkGfm from \"remark-gfm@4.0.1\";\nimport type { MarkdownRendererProps } from \"veryfront/markdown\";\n\n/**\n * Rich Markdown for assistant answers.\n *\n * `veryfront/markdown` presents plain source until a renderer is installed, so\n * this component supplies one. Swap in any renderer that accepts\n * `MarkdownRendererProps` to change how answers are parsed and rendered.\n */\nexport function MarkdownRenderer({ source }: MarkdownRendererProps): React.JSX.Element {\n return <ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>;\n}\n",
|
|
107
107
|
"app/page.tsx": "export default function LandingPage(): React.JSX.Element {\n return (\n <div className=\"min-h-screen bg-white dark:bg-neutral-950\">\n {/* Nav */}\n <nav className=\"border-b border-neutral-100 dark:border-neutral-900\">\n <div className=\"max-w-5xl mx-auto flex items-center justify-between px-6 h-14\">\n <span className=\"font-semibold text-neutral-900 dark:text-white\">\n AI SaaS\n </span>\n <div className=\"flex items-center gap-4\">\n <a\n href=\"/login\"\n className=\"text-sm text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors\"\n >\n Sign in\n </a>\n <a\n href=\"/login\"\n className=\"text-sm px-4 py-1.5 bg-neutral-900 dark:bg-white text-white dark:text-neutral-900 rounded-full font-medium hover:opacity-90 transition-opacity\"\n >\n Get started\n </a>\n </div>\n </div>\n </nav>\n\n {/* Hero */}\n <main className=\"max-w-5xl mx-auto px-6\">\n <div className=\"pt-24 pb-16 text-center\">\n <h1 className=\"text-4xl md:text-5xl font-bold tracking-tight text-neutral-900 dark:text-white\">\n Your AI-powered platform\n </h1>\n <p className=\"mt-4 text-lg text-neutral-500 dark:text-neutral-400 max-w-lg mx-auto\">\n Built with Veryfront. Agents, tools, and memory are ready for production.\n </p>\n <div className=\"mt-8 flex gap-3 justify-center\">\n <a\n href=\"/login\"\n className=\"px-6 py-2.5 bg-neutral-900 dark:bg-white text-white dark:text-neutral-900 rounded-full font-medium hover:opacity-90 transition-opacity\"\n >\n Start free\n </a>\n <a\n href=\"https://veryfront.com/docs/code/guides\"\n className=\"px-6 py-2.5 border border-neutral-200 dark:border-neutral-800 text-neutral-700 dark:text-neutral-300 rounded-full font-medium hover:bg-neutral-50 dark:hover:bg-neutral-900 transition-colors\"\n >\n Documentation\n </a>\n </div>\n </div>\n\n {/* Features */}\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6 py-16 border-t border-neutral-100 dark:border-neutral-900\">\n {[\n {\n title: \"AI Agents\",\n desc:\n \"Define agents with tools, memory, and streaming. Veryfront auto-discovers them from your project.\",\n },\n {\n title: \"Per-User Memory\",\n desc: \"Each user gets their own conversation history, persisted across sessions.\",\n },\n {\n title: \"Production Ready\",\n desc:\n \"Use auth, rate limiting, and deployment to ship to production with one command.\",\n },\n ].map(({ title, desc }) => (\n <div key={title}>\n <h3 className=\"font-medium text-neutral-900 dark:text-white\">\n {title}\n </h3>\n <p className=\"mt-1 text-sm text-neutral-500 dark:text-neutral-400\">\n {desc}\n </p>\n </div>\n ))}\n </div>\n </main>\n </div>\n );\n}\n",
|
|
108
108
|
"globals.css": "@import \"tailwindcss\";\n",
|
|
109
109
|
"public/favicon.svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\">\n <rect width=\"64\" height=\"64\" fill=\"#fff\"/>\n <circle cx=\"32\" cy=\"32\" r=\"20\" fill=\"#000\"/>\n</svg>\n",
|
|
110
110
|
"README.md": "# SaaS Starter\n\nA SaaS-shaped starter with authentication, conversation memory, and a full UI.\n\n## What's included\n\n- Landing page with feature highlights\n- OAuth login (Google and GitHub)\n- Dashboard with conversation sidebar\n- Per-user conversation memory persisted across sessions\n\n## Structure\n\n```\nagents/assistant.ts Agent with conversation memory\ntools/search.ts Placeholder domain search\napp/\n api/ag-ui/route.ts AG-UI endpoint\n page.tsx Landing page\n login/page.tsx OAuth login\n dashboard/page.tsx Chat with sidebar\n```\n\nThis starter is not production-ready.\n",
|
|
111
111
|
"tools/search.ts": "import { tool } from \"veryfront/tool\";\nimport { defineSchema } from \"veryfront/schemas\";\n\nexport default tool({\n id: \"search\",\n description: \"Search your knowledge base\",\n inputSchema: defineSchema((v) =>\n v.object({\n query: v.string().describe(\"Search query\"),\n })\n )(),\n execute: async ({ query }) => {\n // Replace with your domain-specific search logic\n return {\n results: [],\n query,\n message: \"Connect your data source for real results.\",\n };\n },\n});\n",
|
|
112
|
-
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"noEmit\": true,\n \"allowImportingTsExtensions\": true,\n \"paths\": {\n \"@/*\": [\n \"./*\"\n ]\n }\n },\n \"include\": [\n \"**/*.ts\",\n \"**/*.tsx\"\n ],\n \"exclude\": [\n \"node_modules\"\n ]\n}\n"
|
|
112
|
+
"tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"jsx\": \"react-jsx\",\n \"skipLibCheck\": true,\n \"esModuleInterop\": true,\n \"noEmit\": true,\n \"allowImportingTsExtensions\": true,\n \"paths\": {\n \"@/*\": [\n \"./*\"\n ],\n \"react-markdown@*\": [\n \"./node_modules/react-markdown\"\n ],\n \"remark-gfm@*\": [\n \"./node_modules/remark-gfm\"\n ]\n }\n },\n \"include\": [\n \"**/*.ts\",\n \"**/*.tsx\"\n ],\n \"exclude\": [\n \"node_modules\"\n ]\n}\n"
|
|
113
113
|
}
|
|
114
114
|
},
|
|
115
115
|
"integration:_base": {
|
package/esm/deno.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../../../src/src/agent/react/use-chat/streaming/handler.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAKV,kBAAkB,EAEnB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../../../src/src/agent/react/use-chat/streaming/handler.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAKV,kBAAkB,EAEnB,MAAM,YAAY,CAAC;AAsCpB,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,cAAc,EACpB,SAAS,EAAE,kBAAkB,GAC5B,OAAO,CAAC,IAAI,CAAC,CA2Df;AAED,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,cAAc,EACpB,SAAS,EAAE,kBAAkB,GAC5B,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
|
|
@@ -7,8 +7,10 @@ import { buildCurrentParts } from "./parts-builder.js";
|
|
|
7
7
|
function createStreamingState() {
|
|
8
8
|
return {
|
|
9
9
|
textBlocks: new Map(),
|
|
10
|
+
closedTextBlocks: [],
|
|
10
11
|
toolCalls: new Map(),
|
|
11
12
|
reasoningBlocks: new Map(),
|
|
13
|
+
closedReasoningBlocks: [],
|
|
12
14
|
steps: new Map(),
|
|
13
15
|
messageParts: [],
|
|
14
16
|
dataParts: [],
|
|
@@ -23,7 +25,7 @@ export async function handleStreamingResponse(body, callbacks) {
|
|
|
23
25
|
const reader = body.getReader();
|
|
24
26
|
const decoder = new TextDecoder();
|
|
25
27
|
const state = createStreamingState();
|
|
26
|
-
const getBuildParts = () => buildCurrentParts(state.textBlocks, state.reasoningBlocks, state.toolCalls, state.steps, state.dataParts);
|
|
28
|
+
const getBuildParts = () => buildCurrentParts(state.textBlocks, state.reasoningBlocks, state.toolCalls, state.steps, state.dataParts, state.closedReasoningBlocks, state.closedTextBlocks);
|
|
27
29
|
let buffer = "";
|
|
28
30
|
// try/finally so a read error or a throwing event handler still releases
|
|
29
31
|
// the reader lock, otherwise the stream stays locked and leaks.
|
|
@@ -74,7 +76,7 @@ export async function handleAgUiStreamingResponse(body, callbacks) {
|
|
|
74
76
|
const decoder = new TextDecoder();
|
|
75
77
|
const decoderState = createAgUiChatEventDecoderState();
|
|
76
78
|
const state = createStreamingState();
|
|
77
|
-
const getBuildParts = () => buildCurrentParts(state.textBlocks, state.reasoningBlocks, state.toolCalls, state.steps, state.dataParts);
|
|
79
|
+
const getBuildParts = () => buildCurrentParts(state.textBlocks, state.reasoningBlocks, state.toolCalls, state.steps, state.dataParts, state.closedReasoningBlocks, state.closedTextBlocks);
|
|
78
80
|
const processDecodedEvents = (events) => {
|
|
79
81
|
for (const event of events) {
|
|
80
82
|
processChatStreamEvent(event, state, callbacks, getBuildParts);
|
|
@@ -264,8 +266,10 @@ function emitUpdate(state, onUpdate, getBuildParts) {
|
|
|
264
266
|
function handleStart(parsed, state, onUpdate, getBuildParts) {
|
|
265
267
|
state.messageId = typeof parsed.messageId === "string" ? parsed.messageId : "";
|
|
266
268
|
state.textBlocks.clear();
|
|
269
|
+
state.closedTextBlocks.length = 0;
|
|
267
270
|
state.toolCalls.clear();
|
|
268
271
|
state.reasoningBlocks.clear();
|
|
272
|
+
state.closedReasoningBlocks.length = 0;
|
|
269
273
|
state.messageParts.length = 0;
|
|
270
274
|
state.dataParts.length = 0;
|
|
271
275
|
state.messageMetadata = {};
|
|
@@ -293,6 +297,19 @@ function handleTextStart(parsed, state) {
|
|
|
293
297
|
if (!state.messageId) {
|
|
294
298
|
state.messageId = getTextMessageId(parsed) ?? state.currentTextId;
|
|
295
299
|
}
|
|
300
|
+
const existing = state.textBlocks.get(state.currentTextId);
|
|
301
|
+
// Same rule as reasoning spans, for the same reason: a start on a block that
|
|
302
|
+
// is still open is a replayed start, so the accumulated text and the order it
|
|
303
|
+
// already holds both survive. Re-creating it here would blank the answer and
|
|
304
|
+
// move it below everything that streamed in between.
|
|
305
|
+
if (existing && existing.state !== "done")
|
|
306
|
+
return;
|
|
307
|
+
// A start after the block closed is a new answer under a reused id. Providers
|
|
308
|
+
// hand out per-step content ids, so step 2 can arrive as `text-0` again.
|
|
309
|
+
if (existing) {
|
|
310
|
+
state.closedTextBlocks.push(existing);
|
|
311
|
+
state.textBlocks.delete(state.currentTextId);
|
|
312
|
+
}
|
|
296
313
|
state.textBlocks.set(state.currentTextId, { text: "", state: "streaming", order: null });
|
|
297
314
|
}
|
|
298
315
|
function handleTextDelta(parsed, state, onUpdate, getBuildParts) {
|
|
@@ -493,6 +510,22 @@ function handleReasoningStart(parsed, state, onUpdate, getBuildParts) {
|
|
|
493
510
|
state.messageId = generateClientId("msg");
|
|
494
511
|
}
|
|
495
512
|
const reasoningId = parsed.id || generateClientId("reasoning");
|
|
513
|
+
const existing = state.reasoningBlocks.get(reasoningId);
|
|
514
|
+
// A start for a span that is still open is a duplicate (a resumed stream
|
|
515
|
+
// replays it). Keep the accumulated text and, crucially, the order it was
|
|
516
|
+
// first assigned — re-creating the block here would move it below every part
|
|
517
|
+
// that streamed in the meantime.
|
|
518
|
+
if (existing && !existing.isComplete) {
|
|
519
|
+
emitUpdate(state, onUpdate, getBuildParts);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
// A start after that id has closed is a genuinely new span. Providers reuse
|
|
523
|
+
// per-step part ids (`reasoning-0` in every step), so the closed span is
|
|
524
|
+
// retired to keep its own text and position instead of being overwritten.
|
|
525
|
+
if (existing) {
|
|
526
|
+
state.closedReasoningBlocks.push(existing);
|
|
527
|
+
state.reasoningBlocks.delete(reasoningId);
|
|
528
|
+
}
|
|
496
529
|
const reasoning = {
|
|
497
530
|
id: reasoningId,
|
|
498
531
|
text: "",
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { ChatMessagePart } from "../types.js";
|
|
2
2
|
import type { OrderedMessagePart, OrderedReasoning, OrderedStep, OrderedToolCall, TextBlock } from "./types.js";
|
|
3
|
-
export declare function buildCurrentParts(textBlocks: Map<string, TextBlock>, reasoningBlocks: Map<string, OrderedReasoning>, toolCalls: Map<string, OrderedToolCall>, steps?: Map<number, OrderedStep>, extraParts?: OrderedMessagePart[]
|
|
3
|
+
export declare function buildCurrentParts(textBlocks: Map<string, TextBlock>, reasoningBlocks: Map<string, OrderedReasoning>, toolCalls: Map<string, OrderedToolCall>, steps?: Map<number, OrderedStep>, extraParts?: OrderedMessagePart[],
|
|
4
|
+
/**
|
|
5
|
+
* Reasoning spans that closed and were then superseded by a later span
|
|
6
|
+
* reusing the same wire id. They are no longer addressable by id but still
|
|
7
|
+
* belong in the transcript at the position they streamed in.
|
|
8
|
+
*/
|
|
9
|
+
closedReasoningBlocks?: readonly OrderedReasoning[],
|
|
10
|
+
/**
|
|
11
|
+
* Text blocks that closed and were then superseded by a later block reusing
|
|
12
|
+
* the same content id. No longer addressable, still part of the answer.
|
|
13
|
+
*/
|
|
14
|
+
closedTextBlocks?: readonly TextBlock[]): ChatMessagePart[];
|
|
4
15
|
//# sourceMappingURL=parts-builder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parts-builder.d.ts","sourceRoot":"","sources":["../../../../../../src/src/agent/react/use-chat/streaming/parts-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,SAAS,EACV,MAAM,YAAY,CAAC;AAOpB,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,EAClC,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAC9C,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,EACvC,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EAChC,UAAU,CAAC,EAAE,kBAAkB,EAAE,
|
|
1
|
+
{"version":3,"file":"parts-builder.d.ts","sourceRoot":"","sources":["../../../../../../src/src/agent/react/use-chat/streaming/parts-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,SAAS,EACV,MAAM,YAAY,CAAC;AAOpB,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,EAClC,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAC9C,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,EACvC,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EAChC,UAAU,CAAC,EAAE,kBAAkB,EAAE;AACjC;;;;GAIG;AACH,qBAAqB,CAAC,EAAE,SAAS,gBAAgB,EAAE;AACnD;;;GAGG;AACH,gBAAgB,CAAC,EAAE,SAAS,SAAS,EAAE,GACtC,eAAe,EAAE,CAanB"}
|
|
@@ -1,7 +1,22 @@
|
|
|
1
|
-
export function buildCurrentParts(textBlocks, reasoningBlocks, toolCalls, steps, extraParts
|
|
1
|
+
export function buildCurrentParts(textBlocks, reasoningBlocks, toolCalls, steps, extraParts,
|
|
2
|
+
/**
|
|
3
|
+
* Reasoning spans that closed and were then superseded by a later span
|
|
4
|
+
* reusing the same wire id. They are no longer addressable by id but still
|
|
5
|
+
* belong in the transcript at the position they streamed in.
|
|
6
|
+
*/
|
|
7
|
+
closedReasoningBlocks,
|
|
8
|
+
/**
|
|
9
|
+
* Text blocks that closed and were then superseded by a later block reusing
|
|
10
|
+
* the same content id. No longer addressable, still part of the answer.
|
|
11
|
+
*/
|
|
12
|
+
closedTextBlocks) {
|
|
2
13
|
const orderedParts = [];
|
|
3
|
-
addTextParts(orderedParts, textBlocks);
|
|
4
|
-
|
|
14
|
+
addTextParts(orderedParts, textBlocks.values());
|
|
15
|
+
if (closedTextBlocks)
|
|
16
|
+
addTextParts(orderedParts, closedTextBlocks);
|
|
17
|
+
addReasoningParts(orderedParts, reasoningBlocks.values());
|
|
18
|
+
if (closedReasoningBlocks)
|
|
19
|
+
addReasoningParts(orderedParts, closedReasoningBlocks);
|
|
5
20
|
addToolParts(orderedParts, toolCalls);
|
|
6
21
|
if (steps)
|
|
7
22
|
addStepParts(orderedParts, steps);
|
|
@@ -16,7 +31,7 @@ function addExtraParts(orderedParts, extraParts) {
|
|
|
16
31
|
}
|
|
17
32
|
}
|
|
18
33
|
function addTextParts(orderedParts, textBlocks) {
|
|
19
|
-
for (const { text, order, state } of textBlocks
|
|
34
|
+
for (const { text, order, state } of textBlocks) {
|
|
20
35
|
if (!text || order === null)
|
|
21
36
|
continue;
|
|
22
37
|
orderedParts.push({
|
|
@@ -26,7 +41,7 @@ function addTextParts(orderedParts, textBlocks) {
|
|
|
26
41
|
}
|
|
27
42
|
}
|
|
28
43
|
function addReasoningParts(orderedParts, reasoningBlocks) {
|
|
29
|
-
for (const { order, text, signature, redactedData, isComplete } of reasoningBlocks
|
|
44
|
+
for (const { order, text, signature, redactedData, isComplete } of reasoningBlocks) {
|
|
30
45
|
orderedParts.push({
|
|
31
46
|
order,
|
|
32
47
|
part: {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Message } from "../types.js";
|
|
2
|
+
import { type ActiveSkillState } from "./skill-policy-enforcement.js";
|
|
3
|
+
/**
|
|
4
|
+
* The single owner of the request-scoped active-skill policy for one agent
|
|
5
|
+
* loop attempt: which skill is active, what it permits, and how that
|
|
6
|
+
* changes when a skill activates or a form input is submitted.
|
|
7
|
+
*
|
|
8
|
+
* Construct one instance per attempt via `hydrate`. Mutate it in place as tool
|
|
9
|
+
* results arrive. Never share an instance across concurrent runs. This module
|
|
10
|
+
* holds no module-level or static mutable state. `executeAgentLoop` and
|
|
11
|
+
* `executeAgentLoopStreaming` each construct their own instance instead of
|
|
12
|
+
* maintaining separate copies of the same policy transitions.
|
|
13
|
+
*/
|
|
14
|
+
export declare class AgentLoopSkillState {
|
|
15
|
+
activeSkillId: ActiveSkillState["activeSkillId"];
|
|
16
|
+
activeSkillPolicy: ActiveSkillState["activeSkillPolicy"];
|
|
17
|
+
activeSkillToolAvailability: ActiveSkillState["activeSkillToolAvailability"];
|
|
18
|
+
activeSkillDelegationOverrides: ActiveSkillState["activeSkillDelegationOverrides"];
|
|
19
|
+
hasSubmittedFormInput: boolean;
|
|
20
|
+
private constructor();
|
|
21
|
+
/** Hydrate request-scoped skill state from replay history. */
|
|
22
|
+
static hydrate(messages: readonly Message[], runtimeContext: Record<string, unknown> | undefined): AgentLoopSkillState;
|
|
23
|
+
/** Fold a successful skill-activation tool result into the active policy. */
|
|
24
|
+
applySuccessfulResult(result: unknown): void;
|
|
25
|
+
/**
|
|
26
|
+
* Narrow the active policy after a form_input tool result, and record the
|
|
27
|
+
* flag when `submitted` reports the form was actually submitted. Callers
|
|
28
|
+
* compute `submitted` with a broader predicate than this method applies
|
|
29
|
+
* internally; they can disagree, so the flag cannot be recomputed without
|
|
30
|
+
* changing behavior.
|
|
31
|
+
*/
|
|
32
|
+
markFormInputSubmitted(toolName: string, result: unknown, submitted: boolean): void;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=agent-loop-skill-state.d.ts.map
|