@agent-native/core 0.79.20 → 0.79.22

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.
Files changed (141) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/docs/content/deployment.md +133 -0
  4. package/corpus/core/docs/content/faq.md +1 -1
  5. package/corpus/core/docs/content/multi-app-workspace.md +2 -0
  6. package/corpus/core/docs/content/tracking.md +3 -3
  7. package/corpus/core/package.json +1 -1
  8. package/corpus/core/src/agent/engine/openai-compatible-endpoint.ts +25 -0
  9. package/corpus/core/src/agent/engine/provider-env-vars.ts +2 -0
  10. package/corpus/core/src/agent/engine/registry.ts +59 -11
  11. package/corpus/core/src/client/FeedbackButton.tsx +11 -1
  12. package/corpus/core/src/client/agent-engine-key.ts +57 -10
  13. package/corpus/core/src/client/analytics.ts +78 -27
  14. package/corpus/core/src/client/blocks/library/HighlightedCode.tsx +1 -1
  15. package/corpus/core/src/client/index.ts +2 -0
  16. package/corpus/core/src/client/session-replay.ts +159 -17
  17. package/corpus/core/src/client/settings/SettingsPanel.tsx +119 -4
  18. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +5 -0
  19. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +8 -3
  20. package/corpus/core/src/scripts/agent-engines/test-agent-engine.ts +54 -6
  21. package/corpus/core/src/server/agent-engine-api-key-route.ts +83 -12
  22. package/corpus/core/src/server/core-routes-plugin.ts +26 -2
  23. package/corpus/core/src/styles/blocks.css +23 -7
  24. package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -1
  25. package/corpus/templates/analytics/app/components/layout/MobileNav.tsx +5 -10
  26. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +22 -4
  27. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +64 -50
  28. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +38 -0
  29. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-sync.ts +89 -0
  30. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +138 -59
  31. package/corpus/templates/analytics/changelog/2026-06-27-dashboard-charts-drag-smoothly-when-rearranging-panels.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-27-dashboard-email-reports-still-send-when-the-live-screenshot-.md +6 -0
  33. package/corpus/templates/analytics/changelog/2026-06-27-fixed-dashboard-edits-so-charts-no-longer-briefly-snap-back-.md +6 -0
  34. package/corpus/templates/analytics/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  35. package/corpus/templates/analytics/changelog/2026-06-27-session-replay-lists-now-show-only-signed-in-recordings-with.md +6 -0
  36. package/corpus/templates/analytics/changelog/2026-06-27-sidebar-loading-placeholders-are-visible-in-light-mode.md +6 -0
  37. package/corpus/templates/analytics/netlify.toml +4 -0
  38. package/corpus/templates/analytics/scripts/seed-session-replay.ts +1 -0
  39. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +1 -0
  40. package/corpus/templates/analytics/server/lib/dashboard-report.ts +120 -29
  41. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +1 -1
  42. package/corpus/templates/analytics/server/lib/session-replay.ts +158 -46
  43. package/corpus/templates/assets/app/components/layout/Layout.tsx +1 -1
  44. package/corpus/templates/assets/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  45. package/corpus/templates/clips/app/components/capture-install-options.tsx +0 -9
  46. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +20 -4
  47. package/corpus/templates/clips/app/hooks/use-desktop-promo.ts +6 -2
  48. package/corpus/templates/clips/changelog/2026-06-27-clips-no-longer-shows-desktop-app-prompts-on-mobile-screens.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-06-27-desktop-storage-setup-flow.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-06-27-recorder-download-options-now-show-the-chrome-extension-and-.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-06-27-recorder-setup-mobile-capture-options.md +6 -0
  52. package/corpus/templates/clips/chrome-extension/package.json +1 -1
  53. package/corpus/templates/clips/chrome-extension/src/sentry.ts +17 -1
  54. package/corpus/templates/clips/desktop/package.json +1 -0
  55. package/corpus/templates/clips/desktop/scripts/check-tauri-prereqs.mjs +45 -0
  56. package/corpus/templates/clips/desktop/src/app.tsx +32 -3
  57. package/corpus/templates/clips/desktop/src/lib/sentry.ts +71 -0
  58. package/corpus/templates/clips/desktop/src/main.tsx +2 -0
  59. package/corpus/templates/clips/desktop/src/styles.css +67 -0
  60. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +2 -1
  61. package/corpus/templates/clips/desktop/src-tauri/build.rs +29 -0
  62. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -0
  63. package/corpus/templates/clips/desktop/src-tauri/src/logfile.rs +1 -0
  64. package/corpus/templates/clips/desktop/src-tauri/src/sentry_report.rs +142 -0
  65. package/corpus/templates/clips/desktop/vite.config.ts +66 -0
  66. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -1
  67. package/corpus/templates/design/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  68. package/corpus/templates/forms/actions/export-responses.ts +3 -2
  69. package/corpus/templates/forms/actions/list-responses.ts +2 -1
  70. package/corpus/templates/forms/actions/response-insights.ts +13 -4
  71. package/corpus/templates/forms/changelog/2026-06-27-anonymous-feedback-no-longer-shows-internal-agent-native-ano.md +6 -0
  72. package/corpus/templates/forms/server/handlers/submissions.ts +5 -8
  73. package/corpus/templates/forms/server/lib/integrations.ts +9 -6
  74. package/corpus/templates/forms/shared/submitter-email.ts +20 -0
  75. package/corpus/templates/forms/shared/types.ts +1 -1
  76. package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +49 -10
  77. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
  78. package/corpus/templates/slides/changelog/2026-06-27-mobile-navigation-chrome-and-motion.md +6 -0
  79. package/dist/agent/engine/openai-compatible-endpoint.d.ts +3 -0
  80. package/dist/agent/engine/openai-compatible-endpoint.d.ts.map +1 -0
  81. package/dist/agent/engine/openai-compatible-endpoint.js +23 -0
  82. package/dist/agent/engine/openai-compatible-endpoint.js.map +1 -0
  83. package/dist/agent/engine/provider-env-vars.d.ts +1 -0
  84. package/dist/agent/engine/provider-env-vars.d.ts.map +1 -1
  85. package/dist/agent/engine/provider-env-vars.js +1 -0
  86. package/dist/agent/engine/provider-env-vars.js.map +1 -1
  87. package/dist/agent/engine/registry.d.ts.map +1 -1
  88. package/dist/agent/engine/registry.js +39 -12
  89. package/dist/agent/engine/registry.js.map +1 -1
  90. package/dist/client/FeedbackButton.d.ts.map +1 -1
  91. package/dist/client/FeedbackButton.js +6 -1
  92. package/dist/client/FeedbackButton.js.map +1 -1
  93. package/dist/client/agent-engine-key.d.ts +14 -0
  94. package/dist/client/agent-engine-key.d.ts.map +1 -1
  95. package/dist/client/agent-engine-key.js +35 -10
  96. package/dist/client/agent-engine-key.js.map +1 -1
  97. package/dist/client/analytics.d.ts.map +1 -1
  98. package/dist/client/analytics.js +71 -24
  99. package/dist/client/analytics.js.map +1 -1
  100. package/dist/client/blocks/library/HighlightedCode.d.ts +1 -1
  101. package/dist/client/blocks/library/HighlightedCode.js +1 -1
  102. package/dist/client/blocks/library/HighlightedCode.js.map +1 -1
  103. package/dist/client/index.d.ts +1 -1
  104. package/dist/client/index.d.ts.map +1 -1
  105. package/dist/client/index.js +1 -1
  106. package/dist/client/index.js.map +1 -1
  107. package/dist/client/session-replay.d.ts +1 -1
  108. package/dist/client/session-replay.d.ts.map +1 -1
  109. package/dist/client/session-replay.js +115 -17
  110. package/dist/client/session-replay.js.map +1 -1
  111. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  112. package/dist/client/settings/SettingsPanel.js +42 -6
  113. package/dist/client/settings/SettingsPanel.js.map +1 -1
  114. package/dist/collab/routes.d.ts +2 -2
  115. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  116. package/dist/notifications/routes.d.ts +1 -1
  117. package/dist/observability/routes.d.ts +7 -7
  118. package/dist/progress/routes.d.ts +1 -1
  119. package/dist/resources/handlers.d.ts +1 -1
  120. package/dist/scripts/agent-engines/manage-agent-engine.d.ts.map +1 -1
  121. package/dist/scripts/agent-engines/manage-agent-engine.js +4 -0
  122. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  123. package/dist/scripts/agent-engines/set-agent-engine.d.ts.map +1 -1
  124. package/dist/scripts/agent-engines/set-agent-engine.js +5 -4
  125. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  126. package/dist/scripts/agent-engines/test-agent-engine.d.ts.map +1 -1
  127. package/dist/scripts/agent-engines/test-agent-engine.js +37 -5
  128. package/dist/scripts/agent-engines/test-agent-engine.js.map +1 -1
  129. package/dist/server/agent-engine-api-key-route.d.ts +5 -5
  130. package/dist/server/agent-engine-api-key-route.d.ts.map +1 -1
  131. package/dist/server/agent-engine-api-key-route.js +71 -11
  132. package/dist/server/agent-engine-api-key-route.js.map +1 -1
  133. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  134. package/dist/server/core-routes-plugin.js +20 -3
  135. package/dist/server/core-routes-plugin.js.map +1 -1
  136. package/dist/styles/blocks.css +23 -7
  137. package/docs/content/deployment.md +133 -0
  138. package/docs/content/faq.md +1 -1
  139. package/docs/content/multi-app-workspace.md +2 -0
  140. package/docs/content/tracking.md +3 -3
  141. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  import * as SelectPrimitive from "@radix-ui/react-select";
2
2
  import {
3
3
  IconChevronDown,
4
+ IconChevronRight,
4
5
  IconCheck,
5
6
  IconExternalLink,
6
7
  IconBrain,
@@ -33,7 +34,7 @@ import React, {
33
34
  } from "react";
34
35
 
35
36
  import { PROVIDER_ENV_PLACEHOLDERS } from "../../agent/engine/provider-env-vars.js";
36
- import { saveAgentEngineApiKey } from "../agent-engine-key.js";
37
+ import { saveAgentEngineProviderSettings } from "../agent-engine-key.js";
37
38
  import { agentNativePath } from "../api-path.js";
38
39
  import { BuilderBMark } from "../builder-mark.js";
39
40
  import {
@@ -619,6 +620,10 @@ function LLMSectionInner({
619
620
  const [currentModel, setCurrentModel] = useState("");
620
621
  const [selectedEngine, setSelectedEngine] = useState("anthropic");
621
622
  const [selectedModel, setSelectedModel] = useState("");
623
+ const [baseUrl, setBaseUrl] = useState("");
624
+ const [baseUrlConfigured, setBaseUrlConfigured] = useState(false);
625
+ const [clearBaseUrl, setClearBaseUrl] = useState(false);
626
+ const [advancedOpen, setAdvancedOpen] = useState(false);
622
627
  const [applyNote, setApplyNote] = useState(false);
623
628
  const [testing, setTesting] = useState(false);
624
629
  const [testResult, setTestResult] = useState<
@@ -651,6 +656,7 @@ function LLMSectionInner({
651
656
  fetch(agentNativePath("/_agent-native/agent-engine/status"))
652
657
  .then((r) => (r.ok ? r.json() : null))
653
658
  .then((data) => {
659
+ setBaseUrlConfigured(Boolean(data?.openAiBaseUrlConfigured));
654
660
  if (
655
661
  data?.configured &&
656
662
  typeof data.engine === "string" &&
@@ -715,6 +721,9 @@ function LLMSectionInner({
715
721
 
716
722
  const engineChanged =
717
723
  selectedEngine !== currentEngine || selectedModel !== currentModel;
724
+ const isOpenAiEngine = selectedEngine === "ai-sdk:openai";
725
+ const endpointChanged = isOpenAiEngine && (!!baseUrl.trim() || clearBaseUrl);
726
+ const providerSettingsChanged = !!apiKey.trim() || endpointChanged;
718
727
 
719
728
  // Hide the Anthropic-via-AI-SDK alias (redundant with the native entry)
720
729
  // and Ollama (no API key to set here). The currently-selected engine is
@@ -732,12 +741,22 @@ function LLMSectionInner({
732
741
  ).map((m) => ({ value: m, label: friendlyModelName(m) }));
733
742
 
734
743
  const handleSave = async () => {
735
- if (!apiKey.trim() || !envVar) return;
744
+ if (!providerSettingsChanged || !envVar) return;
736
745
  setSaving(true);
737
746
  try {
738
- await saveAgentEngineApiKey({ key: envVar, apiKey });
747
+ const nextBaseUrl = isOpenAiEngine ? baseUrl.trim() : "";
748
+ await saveAgentEngineProviderSettings({
749
+ key: envVar,
750
+ ...(apiKey.trim() ? { apiKey } : {}),
751
+ ...(nextBaseUrl ? { baseUrl: nextBaseUrl } : {}),
752
+ ...(isOpenAiEngine && clearBaseUrl ? { clearBaseUrl: true } : {}),
753
+ });
739
754
  setSaved(true);
740
755
  setApiKey("");
756
+ setBaseUrl("");
757
+ setClearBaseUrl(false);
758
+ if (nextBaseUrl) setBaseUrlConfigured(true);
759
+ if (clearBaseUrl) setBaseUrlConfigured(false);
741
760
  refreshSettingsStatus();
742
761
  notifyConfigChanged();
743
762
  setTimeout(() => setSaved(false), 2000);
@@ -885,6 +904,9 @@ function LLMSectionInner({
885
904
  const info = engines.find((e) => e.name === val);
886
905
  setSelectedModel(info?.defaultModel ?? "");
887
906
  setApiKey("");
907
+ setBaseUrl("");
908
+ setClearBaseUrl(false);
909
+ setAdvancedOpen(false);
888
910
  }}
889
911
  />
890
912
 
@@ -920,6 +942,99 @@ function LLMSectionInner({
920
942
  )}
921
943
  </div>
922
944
 
945
+ {isOpenAiEngine && (
946
+ <div className="border-t border-border/70 pt-2">
947
+ <button
948
+ type="button"
949
+ onClick={() => setAdvancedOpen((v) => !v)}
950
+ className="flex w-full cursor-pointer items-center justify-between gap-2 rounded px-0.5 py-1 text-left hover:text-foreground"
951
+ >
952
+ <span className="inline-flex min-w-0 items-center gap-1.5 text-[11px] font-medium text-foreground">
953
+ {advancedOpen ? (
954
+ <IconChevronDown size={12} />
955
+ ) : (
956
+ <IconChevronRight
957
+ size={12}
958
+ className="rtl:-scale-x-100"
959
+ />
960
+ )}
961
+ Advanced
962
+ </span>
963
+ <span className="truncate text-[10px] text-muted-foreground">
964
+ OpenAI-compatible endpoint
965
+ </span>
966
+ </button>
967
+
968
+ {advancedOpen && (
969
+ <div className="mt-1.5 space-y-1.5">
970
+ <div className="flex items-center justify-between gap-2">
971
+ <p className="text-[12px] font-medium text-foreground">
972
+ Endpoint URL
973
+ </p>
974
+ <span className="text-[10px] text-muted-foreground">
975
+ {baseUrlConfigured ? "Configured" : "Optional"}
976
+ </span>
977
+ </div>
978
+ <input
979
+ type="url"
980
+ value={baseUrl}
981
+ onChange={(e) => {
982
+ setBaseUrl(e.target.value);
983
+ if (e.target.value.trim()) setClearBaseUrl(false);
984
+ }}
985
+ onKeyDown={(e) => {
986
+ if (e.key === "Enter") handleSave();
987
+ }}
988
+ placeholder={
989
+ baseUrlConfigured
990
+ ? "Leave blank to keep current endpoint"
991
+ : "https://gateway.example/v1"
992
+ }
993
+ disabled={clearBaseUrl}
994
+ spellCheck={false}
995
+ autoComplete="off"
996
+ className="flex h-9 w-full rounded-md border border-border bg-background px-3 text-[12px] text-foreground outline-none transition-colors hover:bg-accent/40 focus:ring-1 focus:ring-accent disabled:opacity-50 placeholder:text-muted-foreground/50"
997
+ style={CONTROL_STYLE}
998
+ />
999
+ <p className="text-[10px] leading-relaxed text-muted-foreground">
1000
+ Use for LiteLLM or another OpenAI-compatible chat
1001
+ gateway. Leave blank for OpenAI.
1002
+ </p>
1003
+ {baseUrlConfigured && (
1004
+ <label className="flex items-center gap-1.5 text-[10px] text-muted-foreground">
1005
+ <input
1006
+ type="checkbox"
1007
+ checked={clearBaseUrl}
1008
+ onChange={(e) => {
1009
+ setClearBaseUrl(e.target.checked);
1010
+ if (e.target.checked) setBaseUrl("");
1011
+ }}
1012
+ className="h-3 w-3 accent-current"
1013
+ />
1014
+ Clear saved endpoint override
1015
+ </label>
1016
+ )}
1017
+ {envVar && envConfigured && endpointChanged && (
1018
+ <button
1019
+ type="button"
1020
+ onClick={handleSave}
1021
+ disabled={saving}
1022
+ className="rounded bg-accent px-2.5 py-1 text-[10px] font-medium text-foreground hover:bg-accent/80 disabled:opacity-40"
1023
+ >
1024
+ {saving ? (
1025
+ <IconLoader2 size={10} className="animate-spin" />
1026
+ ) : saved ? (
1027
+ <IconCheck size={10} />
1028
+ ) : (
1029
+ "Save endpoint"
1030
+ )}
1031
+ </button>
1032
+ )}
1033
+ </div>
1034
+ )}
1035
+ </div>
1036
+ )}
1037
+
923
1038
  {envVar && envConfigured ? (
924
1039
  <div className="flex items-center gap-1.5 text-[10px] text-green-500">
925
1040
  <IconCheck size={10} />
@@ -939,7 +1054,7 @@ function LLMSectionInner({
939
1054
  />
940
1055
  <button
941
1056
  onClick={handleSave}
942
- disabled={!apiKey.trim() || saving}
1057
+ disabled={!providerSettingsChanged || saving}
943
1058
  className="rounded bg-accent px-2 py-1 text-[10px] font-medium text-foreground hover:bg-accent/80 disabled:opacity-40"
944
1059
  >
945
1060
  {saving ? (
@@ -56,6 +56,11 @@ export const tool: ActionTool = {
56
56
  description:
57
57
  "Model ID (e.g. 'gpt-5.5', 'claude-sonnet-4-6', 'gemini-3-1-pro'). Required for \"set-app-default\"; optional for \"set\" and \"test\" where it defaults to the engine's default model.",
58
58
  },
59
+ baseUrl: {
60
+ type: "string",
61
+ description:
62
+ 'Optional OpenAI-compatible endpoint URL for action="test" with engine="ai-sdk:openai". Saved endpoint settings are used when omitted.',
63
+ },
59
64
  appId: {
60
65
  type: "string",
61
66
  description:
@@ -6,6 +6,7 @@ import {
6
6
  listAgentEngines,
7
7
  getAgentEngineEntry,
8
8
  isAgentEnginePackageInstalled,
9
+ isStoredEngineUsableForRequest,
9
10
  normalizeModelForEngine,
10
11
  registerBuiltinEngines,
11
12
  } from "../../agent/engine/index.js";
@@ -67,9 +68,13 @@ export async function run(args: Record<string, string>): Promise<string> {
67
68
  entry.supportedModels.length === 0 ||
68
69
  entry.supportedModels.includes(resolvedModel);
69
70
 
70
- const missingEnvVars = entry.requiredEnvVars.filter((v) => !process.env[v]);
71
- if (missingEnvVars.length > 0) {
72
- return `Warning: Engine "${engineName}" requires the following environment variables which are not set: ${missingEnvVars.join(", ")}. The engine will fail at runtime without them.`;
71
+ const usable = await isStoredEngineUsableForRequest(
72
+ { engine: engineName },
73
+ entry,
74
+ );
75
+ if (!usable) {
76
+ const missingEnvVars = entry.requiredEnvVars.join(", ");
77
+ return `Warning: Engine "${engineName}" requires the following credentials which are not configured for this request: ${missingEnvVars}. The engine will fail at runtime without them.`;
73
78
  }
74
79
 
75
80
  await putSetting("agent-engine", {
@@ -5,8 +5,18 @@
5
5
  import {
6
6
  getAgentEngineEntry,
7
7
  registerBuiltinEngines,
8
+ type AgentEngineEntry,
8
9
  } from "../../agent/engine/index.js";
10
+ import {
11
+ normalizeOpenAiBaseUrl,
12
+ OPENAI_BASE_URL_ENV_VAR,
13
+ } from "../../agent/engine/openai-compatible-endpoint.js";
9
14
  import type { ActionTool } from "../../agent/types.js";
15
+ import {
16
+ canUseDeployCredentialFallbackForRequest,
17
+ readDeployCredentialEnv,
18
+ resolveSecret,
19
+ } from "../../server/credential-provider.js";
10
20
 
11
21
  export const tool: ActionTool = {
12
22
  description:
@@ -24,11 +34,54 @@ export const tool: ActionTool = {
24
34
  description:
25
35
  "Model to use for the test. Defaults to the engine's default model.",
26
36
  },
37
+ baseUrl: {
38
+ type: "string",
39
+ description:
40
+ 'Optional OpenAI-compatible endpoint URL to test with "ai-sdk:openai". Uses the saved endpoint when omitted.',
41
+ },
27
42
  },
28
43
  required: [],
29
44
  },
30
45
  };
31
46
 
47
+ async function resolveAgentEngineSecret(
48
+ key: string,
49
+ ): Promise<string | undefined> {
50
+ try {
51
+ const value = await resolveSecret(key);
52
+ if (value) return value;
53
+ } catch {
54
+ // Fall through to deploy env when this request is allowed to use it.
55
+ }
56
+ return canUseDeployCredentialFallbackForRequest()
57
+ ? readDeployCredentialEnv(key)
58
+ : undefined;
59
+ }
60
+
61
+ async function createEngineConfig(
62
+ entry: AgentEngineEntry,
63
+ args: Record<string, string>,
64
+ ): Promise<Record<string, unknown>> {
65
+ const config: Record<string, unknown> = {
66
+ apiKey:
67
+ entry.requiredEnvVars.length > 0
68
+ ? await resolveAgentEngineSecret(entry.requiredEnvVars[0])
69
+ : undefined,
70
+ allowEnvFallback: canUseDeployCredentialFallbackForRequest(),
71
+ };
72
+
73
+ if (entry.name === "ai-sdk:openai") {
74
+ const rawBaseUrl = args.baseUrl?.trim()
75
+ ? args.baseUrl
76
+ : await resolveAgentEngineSecret(OPENAI_BASE_URL_ENV_VAR);
77
+ if (rawBaseUrl) {
78
+ config.baseUrl = normalizeOpenAiBaseUrl(rawBaseUrl);
79
+ }
80
+ }
81
+
82
+ return config;
83
+ }
84
+
32
85
  export async function run(args: Record<string, string>): Promise<string> {
33
86
  registerBuiltinEngines();
34
87
 
@@ -44,12 +97,7 @@ export async function run(args: Record<string, string>): Promise<string> {
44
97
  const model = args.model ?? entry.defaultModel;
45
98
 
46
99
  try {
47
- const engine = entry.create({
48
- apiKey:
49
- entry.requiredEnvVars.length > 0
50
- ? process.env[entry.requiredEnvVars[0]]
51
- : undefined,
52
- });
100
+ const engine = entry.create(await createEngineConfig(entry, args));
53
101
 
54
102
  const start = Date.now();
55
103
  const controller = new AbortController();
@@ -5,9 +5,13 @@ import {
5
5
  type H3Event,
6
6
  } from "h3";
7
7
 
8
- import { PROVIDER_ENV_META } from "../agent/engine/provider-env-vars.js";
8
+ import { normalizeOpenAiBaseUrl } from "../agent/engine/openai-compatible-endpoint.js";
9
+ import {
10
+ OPENAI_BASE_URL_ENV_VAR,
11
+ PROVIDER_ENV_META,
12
+ } from "../agent/engine/provider-env-vars.js";
9
13
  import { getOrgContext } from "../org/context.js";
10
- import { writeAppSecret } from "../secrets/storage.js";
14
+ import { deleteAppSecret, writeAppSecret } from "../secrets/storage.js";
11
15
  import { getSession } from "./auth.js";
12
16
  import { readBody } from "./h3-helpers.js";
13
17
 
@@ -30,7 +34,9 @@ export function normalizeAgentEngineApiKeyPayload(body: unknown):
30
34
  | {
31
35
  ok: true;
32
36
  key: string;
33
- value: string;
37
+ value?: string;
38
+ baseUrl?: string;
39
+ clearBaseUrl: boolean;
34
40
  scope: AgentEngineApiKeyScope;
35
41
  }
36
42
  | { ok: false; statusCode: number; error: string } {
@@ -40,6 +46,9 @@ export function normalizeAgentEngineApiKeyPayload(body: unknown):
40
46
  provider?: unknown;
41
47
  value?: unknown;
42
48
  apiKey?: unknown;
49
+ baseUrl?: unknown;
50
+ endpointUrl?: unknown;
51
+ clearBaseUrl?: unknown;
43
52
  scope?: unknown;
44
53
  };
45
54
 
@@ -63,8 +72,48 @@ export function normalizeAgentEngineApiKeyPayload(body: unknown):
63
72
  : typeof raw.apiKey === "string"
64
73
  ? raw.apiKey.trim()
65
74
  : "";
66
- if (!value) {
67
- return { ok: false, statusCode: 400, error: "value is required" };
75
+
76
+ const rawBaseUrl =
77
+ typeof raw.baseUrl === "string"
78
+ ? raw.baseUrl
79
+ : typeof raw.endpointUrl === "string"
80
+ ? raw.endpointUrl
81
+ : "";
82
+ let baseUrl: string | undefined;
83
+ if (rawBaseUrl.trim()) {
84
+ if (key !== PROVIDER_TO_ENV_VAR.get("openai")) {
85
+ return {
86
+ ok: false,
87
+ statusCode: 400,
88
+ error: "Endpoint URL is only supported for OpenAI.",
89
+ };
90
+ }
91
+ try {
92
+ baseUrl = normalizeOpenAiBaseUrl(rawBaseUrl);
93
+ } catch (err) {
94
+ return {
95
+ ok: false,
96
+ statusCode: 400,
97
+ error: err instanceof Error ? err.message : String(err),
98
+ };
99
+ }
100
+ }
101
+
102
+ const clearBaseUrl = raw.clearBaseUrl === true && baseUrl == null;
103
+ if (clearBaseUrl && key !== PROVIDER_TO_ENV_VAR.get("openai")) {
104
+ return {
105
+ ok: false,
106
+ statusCode: 400,
107
+ error: "Endpoint URL is only supported for OpenAI.",
108
+ };
109
+ }
110
+
111
+ if (!value && !baseUrl && !clearBaseUrl) {
112
+ return {
113
+ ok: false,
114
+ statusCode: 400,
115
+ error: "value or baseUrl is required",
116
+ };
68
117
  }
69
118
 
70
119
  if (raw.scope != null && raw.scope !== "user" && raw.scope !== "org") {
@@ -78,7 +127,9 @@ export function normalizeAgentEngineApiKeyPayload(body: unknown):
78
127
  return {
79
128
  ok: true,
80
129
  key,
81
- value,
130
+ ...(value ? { value } : {}),
131
+ ...(baseUrl ? { baseUrl } : {}),
132
+ clearBaseUrl,
82
133
  scope: raw.scope === "org" ? "org" : "user",
83
134
  };
84
135
  }
@@ -144,16 +195,36 @@ export function createAgentEngineApiKeyHandler() {
144
195
  return { error: resolved.error };
145
196
  }
146
197
 
147
- await writeAppSecret({
148
- key: payload.key,
149
- value: payload.value,
150
- scope: resolved.target.scope,
151
- scopeId: resolved.target.scopeId,
152
- });
198
+ if (payload.value) {
199
+ await writeAppSecret({
200
+ key: payload.key,
201
+ value: payload.value,
202
+ scope: resolved.target.scope,
203
+ scopeId: resolved.target.scopeId,
204
+ });
205
+ }
206
+
207
+ if (payload.baseUrl) {
208
+ await writeAppSecret({
209
+ key: OPENAI_BASE_URL_ENV_VAR,
210
+ value: payload.baseUrl,
211
+ scope: resolved.target.scope,
212
+ scopeId: resolved.target.scopeId,
213
+ });
214
+ } else if (payload.clearBaseUrl) {
215
+ await deleteAppSecret({
216
+ key: OPENAI_BASE_URL_ENV_VAR,
217
+ scope: resolved.target.scope,
218
+ scopeId: resolved.target.scopeId,
219
+ });
220
+ }
153
221
 
154
222
  return {
155
223
  ok: true,
156
224
  key: payload.key,
225
+ ...(payload.baseUrl || payload.clearBaseUrl
226
+ ? { baseUrlKey: OPENAI_BASE_URL_ENV_VAR }
227
+ : {}),
157
228
  scope: resolved.target.scope,
158
229
  };
159
230
  });
@@ -14,7 +14,10 @@ import { readMultipartFormData } from "h3";
14
14
 
15
15
  import { DEFAULT_MODEL } from "../agent/default-model.js";
16
16
  import { registerBuiltinEngines } from "../agent/engine/builtin.js";
17
- import { PROVIDER_ENV_META } from "../agent/engine/provider-env-vars.js";
17
+ import {
18
+ OPENAI_BASE_URL_ENV_VAR,
19
+ PROVIDER_ENV_META,
20
+ } from "../agent/engine/provider-env-vars.js";
18
21
  import {
19
22
  isAgentEngineSettingConfigured,
20
23
  getAgentEngineEntry,
@@ -123,6 +126,7 @@ import {
123
126
  import type { EnvKeyConfig } from "./create-server.js";
124
127
  import {
125
128
  canUseDeployCredentialFallbackForRequest,
129
+ readDeployCredentialEnv,
126
130
  resolveSecret,
127
131
  } from "./credential-provider.js";
128
132
  import { createEmbedStartRouteHandler } from "./embed-route.js";
@@ -2311,6 +2315,20 @@ export function createCoreRoutesPlugin(
2311
2315
  /* org module not present in this template */
2312
2316
  }
2313
2317
  }
2318
+ const openAiBaseUrlConfigured = await runWithRequestContext(
2319
+ { userEmail, orgId },
2320
+ async () => {
2321
+ try {
2322
+ if (await resolveSecret(OPENAI_BASE_URL_ENV_VAR)) return true;
2323
+ } catch {
2324
+ /* fall through to deployment env when allowed */
2325
+ }
2326
+ return (
2327
+ canUseDeployCredentialFallbackForRequest() &&
2328
+ !!readDeployCredentialEnv(OPENAI_BASE_URL_ENV_VAR)
2329
+ );
2330
+ },
2331
+ );
2314
2332
  const stored = (await getSetting("agent-engine")) as {
2315
2333
  engine?: string;
2316
2334
  model?: string;
@@ -2323,6 +2341,7 @@ export function createCoreRoutesPlugin(
2323
2341
  engine,
2324
2342
  model: stored?.model ?? entry?.defaultModel ?? DEFAULT_MODEL,
2325
2343
  source: "settings" as const,
2344
+ openAiBaseUrlConfigured,
2326
2345
  };
2327
2346
  }
2328
2347
  const envEntry = process.env.AGENT_ENGINE
@@ -2338,7 +2357,7 @@ export function createCoreRoutesPlugin(
2338
2357
  ),
2339
2358
  );
2340
2359
  if (!envUsable) {
2341
- return { configured: false };
2360
+ return { configured: false, openAiBaseUrlConfigured };
2342
2361
  }
2343
2362
  return {
2344
2363
  configured: true,
@@ -2346,6 +2365,7 @@ export function createCoreRoutesPlugin(
2346
2365
  model: envEntry.defaultModel ?? DEFAULT_MODEL,
2347
2366
  source: "env" as const,
2348
2367
  envVar: "AGENT_ENGINE",
2368
+ openAiBaseUrlConfigured,
2349
2369
  };
2350
2370
  }
2351
2371
  // Per-user app_secrets — a user who connected Builder (or pasted
@@ -2363,6 +2383,7 @@ export function createCoreRoutesPlugin(
2363
2383
  model: detectedFromUser.defaultModel ?? DEFAULT_MODEL,
2364
2384
  source: "app_secrets" as const,
2365
2385
  envVar: detectedFromUser.requiredEnvVars[0],
2386
+ openAiBaseUrlConfigured,
2366
2387
  };
2367
2388
  }
2368
2389
  if (stored && typeof stored.engine === "string") {
@@ -2379,6 +2400,7 @@ export function createCoreRoutesPlugin(
2379
2400
  model: stored.model ?? entry.defaultModel ?? DEFAULT_MODEL,
2380
2401
  source: "env" as const,
2381
2402
  envVar: entry.requiredEnvVars[0],
2403
+ openAiBaseUrlConfigured,
2382
2404
  };
2383
2405
  }
2384
2406
  }
@@ -2389,6 +2411,7 @@ export function createCoreRoutesPlugin(
2389
2411
  model: detectedFromUser.defaultModel ?? DEFAULT_MODEL,
2390
2412
  source: "app_secrets" as const,
2391
2413
  envVar: detectedFromUser.requiredEnvVars[0],
2414
+ openAiBaseUrlConfigured,
2392
2415
  };
2393
2416
  }
2394
2417
  const canUseDeployEnv = await runWithRequestContext(
@@ -2403,6 +2426,7 @@ export function createCoreRoutesPlugin(
2403
2426
  model: detected.defaultModel ?? DEFAULT_MODEL,
2404
2427
  source: "env" as const,
2405
2428
  envVar: detected.requiredEnvVars[0],
2429
+ openAiBaseUrlConfigured,
2406
2430
  };
2407
2431
  }
2408
2432
  } catch {}
@@ -1231,6 +1231,7 @@
1231
1231
  --wf-muted: var(--plan-muted, hsl(var(--muted-foreground)));
1232
1232
  --wf-line: var(--plan-line, hsl(var(--border)));
1233
1233
  --wf-sketch: var(--plan-muted-line, hsl(var(--border)));
1234
+ --wf-diagram-line: var(--wf-line);
1234
1235
  --wf-paper: var(--plan-document, hsl(var(--background)));
1235
1236
  --wf-card: var(--plan-block, hsl(var(--card)));
1236
1237
  --wf-accent: hsl(var(--ring));
@@ -1256,6 +1257,10 @@
1256
1257
  line-height: 1.4;
1257
1258
  }
1258
1259
 
1260
+ .plan-diagram-frame[data-theme="dark"] {
1261
+ --wf-diagram-line: color-mix(in srgb, var(--wf-sketch) 72%, var(--wf-line));
1262
+ }
1263
+
1259
1264
  .plan-diagram-frame[data-style="sketchy"] {
1260
1265
  --wf-font: var(--wf-font-hand);
1261
1266
  line-height: 1.32;
@@ -1275,9 +1280,7 @@
1275
1280
  }
1276
1281
 
1277
1282
  .plan-diagram-frame-content
1278
- > :is(.diagram-panel, .diagram-card, .diagram-box):not([data-rough]):not(
1279
- [data-diagram-frame]
1280
- ) {
1283
+ > .diagram-panel:not([data-rough]):not([data-diagram-frame]) {
1281
1284
  --rough-skip: 1;
1282
1285
  background: transparent;
1283
1286
  border-color: transparent;
@@ -1318,7 +1321,8 @@
1318
1321
 
1319
1322
  .plan-diagram-frame
1320
1323
  :is(.diagram-panel, .diagram-card, .diagram-node, .diagram-box) {
1321
- border: 1.25px solid var(--wf-line);
1324
+ --rough-stroke: var(--wf-diagram-line);
1325
+ border: 1.25px solid var(--wf-diagram-line);
1322
1326
  border-radius: var(--wf-radius);
1323
1327
  background: var(--wf-card);
1324
1328
  }
@@ -1333,7 +1337,8 @@
1333
1337
  [class*="panel"],
1334
1338
  [class*="box"]
1335
1339
  ) {
1336
- border-color: var(--wf-line) !important;
1340
+ --rough-stroke: var(--wf-diagram-line);
1341
+ border-color: var(--wf-diagram-line) !important;
1337
1342
  background: var(--wf-card) !important;
1338
1343
  color: var(--wf-ink) !important;
1339
1344
  }
@@ -1366,7 +1371,8 @@
1366
1371
 
1367
1372
  .plan-diagram-frame
1368
1373
  :is(.diagram-pill, [class*="pill"], [class*="badge"], [class*="chip"]) {
1369
- border-color: var(--wf-line) !important;
1374
+ --rough-stroke: var(--wf-diagram-line);
1375
+ border-color: var(--wf-diagram-line) !important;
1370
1376
  background: var(--wf-paper) !important;
1371
1377
  color: var(--wf-ink) !important;
1372
1378
  }
@@ -1411,7 +1417,7 @@
1411
1417
  * variant used across the docs diagrams stays pill-shaped, not a full bar. */
1412
1418
  width: fit-content;
1413
1419
  max-width: 100%;
1414
- border: 1px solid var(--wf-line);
1420
+ border: 1px solid var(--wf-diagram-line);
1415
1421
  border-radius: 999px;
1416
1422
  padding: 3px 11px;
1417
1423
  color: var(--wf-ink);
@@ -1453,6 +1459,16 @@
1453
1459
  [data-rough],
1454
1460
  hr
1455
1461
  ) {
1462
+ border-color: color-mix(
1463
+ in srgb,
1464
+ var(--wf-diagram-line) 54%,
1465
+ transparent
1466
+ ) !important;
1467
+ }
1468
+
1469
+ .plan-diagram-frame[data-rough-ready]
1470
+ .plan-diagram-frame-content
1471
+ > .diagram-panel:not([data-rough]):not([data-diagram-frame]) {
1456
1472
  border-color: transparent !important;
1457
1473
  }
1458
1474
 
@@ -26,7 +26,7 @@ export default defineAction({
26
26
  app: z.string().optional().describe("Optional app filter"),
27
27
  template: z.string().optional().describe("Optional template filter"),
28
28
  sessionId: z.string().optional().describe("Optional analytics session id"),
29
- userId: z.string().optional().describe("Optional signed-in user id"),
29
+ userId: z.string().optional().describe("Optional signed-in user email"),
30
30
  anonymousId: z.string().optional().describe("Optional anonymous user id"),
31
31
  path: z.string().optional().describe("Optional exact path filter"),
32
32
  from: z
@@ -1,5 +1,5 @@
1
1
  import { useT } from "@agent-native/core/client";
2
- import { IconMenu, IconChartBar } from "@tabler/icons-react";
2
+ import { IconMenu } from "@tabler/icons-react";
3
3
  import { useState, useEffect } from "react";
4
4
  import { useLocation } from "react-router";
5
5
 
@@ -18,7 +18,7 @@ export function MobileNav() {
18
18
  }, [location.pathname]);
19
19
 
20
20
  return (
21
- <div className="flex h-12 shrink-0 items-center border-b border-border bg-sidebar px-4 md:hidden">
21
+ <div className="flex h-12 shrink-0 items-center border-b border-border bg-background px-4 md:hidden">
22
22
  <button
23
23
  onClick={() => setOpen(true)}
24
24
  className="me-3 p-2.5 -ms-1 rounded-md hover:bg-sidebar-accent/50"
@@ -26,14 +26,9 @@ export function MobileNav() {
26
26
  >
27
27
  <IconMenu className="h-5 w-5 text-foreground" />
28
28
  </button>
29
- <div className="flex items-center gap-2">
30
- <div className="flex h-7 w-7 items-center justify-center rounded-lg bg-primary text-primary-foreground">
31
- <IconChartBar className="h-4 w-4" />
32
- </div>
33
- <span className="text-base font-bold tracking-tight">
34
- {t("navigation.brand")}
35
- </span>
36
- </div>
29
+ <span className="text-base font-bold tracking-tight">
30
+ {t("navigation.brand")}
31
+ </span>
37
32
 
38
33
  <Sheet open={open} onOpenChange={setOpen}>
39
34
  <SheetContent side="left" className="p-0 w-[280px]">