@agent-native/core 0.158.10 → 0.159.1

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 (76) hide show
  1. package/corpus/templates/clips/app/components/recorder/recording-toolbar.tsx +2 -2
  2. package/corpus/templates/clips/app/i18n/en-US.ts +7 -2
  3. package/corpus/templates/clips/app/routes/record.tsx +147 -5
  4. package/dist/a2a/auth-policy.d.ts +0 -6
  5. package/dist/a2a/auth-policy.js +2 -1
  6. package/dist/a2a/client.js +10 -12
  7. package/dist/agent/durable-background.js +3 -2
  8. package/dist/agent/engine/registry.js +5 -4
  9. package/dist/agent/run-manager.js +8 -20
  10. package/dist/app-config/a2a.d.ts +14 -0
  11. package/dist/app-config/a2a.js +42 -0
  12. package/dist/app-config/agent.d.ts +18 -0
  13. package/dist/app-config/agent.js +69 -0
  14. package/dist/app-config/app.d.ts +33 -0
  15. package/dist/app-config/app.js +100 -0
  16. package/dist/app-config/describe.d.ts +26 -0
  17. package/dist/app-config/describe.js +61 -0
  18. package/dist/app-config/env-layer.d.ts +37 -0
  19. package/dist/app-config/env-layer.js +136 -0
  20. package/dist/app-config/index.d.ts +2 -0
  21. package/dist/app-config/index.js +2 -0
  22. package/dist/app-config/integrations.d.ts +5 -0
  23. package/dist/app-config/integrations.js +11 -0
  24. package/dist/app-config/private-blob.d.ts +14 -0
  25. package/dist/app-config/private-blob.js +19 -0
  26. package/dist/app-config/schema.d.ts +55 -0
  27. package/dist/app-config/schema.js +31 -0
  28. package/dist/app-config/store.d.ts +42 -0
  29. package/dist/app-config/store.js +104 -0
  30. package/dist/app-config/workspace.d.ts +21 -0
  31. package/dist/app-config/workspace.js +35 -0
  32. package/dist/cli/agent.js +2 -1
  33. package/dist/cli/code-agent-executor.js +5 -4
  34. package/dist/client/analytics.d.ts +9 -0
  35. package/dist/client/frame.js +11 -1
  36. package/dist/deploy/build.js +36 -0
  37. package/dist/integrations/a2a-continuation-processor.js +12 -9
  38. package/dist/integrations/adapters/email.js +2 -1
  39. package/dist/integrations/adapters/telegram.js +2 -1
  40. package/dist/integrations/adapters/whatsapp.js +2 -1
  41. package/dist/integrations/webhook-handler.d.ts +7 -2
  42. package/dist/integrations/webhook-handler.js +9 -31
  43. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  44. package/dist/notifications/routes.d.ts +3 -3
  45. package/dist/observability/routes.d.ts +6 -6
  46. package/dist/onboarding/app-profile.js +2 -4
  47. package/dist/onboarding/default-steps.js +4 -4
  48. package/dist/private-blob/registry.d.ts +5 -0
  49. package/dist/private-blob/registry.js +19 -8
  50. package/dist/provider-api/actions/custom-provider-registration.d.ts +13 -13
  51. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  52. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  53. package/dist/scripts/agent-engines/list-agent-engines.js +4 -2
  54. package/dist/secrets/routes.d.ts +3 -3
  55. package/dist/server/agent-chat/action-filters-a2a.js +5 -4
  56. package/dist/server/agent-chat/run-code-tools.js +3 -4
  57. package/dist/server/agent-chat-plugin.js +4 -3
  58. package/dist/server/agent-discovery.js +7 -5
  59. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  60. package/dist/server/app-origin-config.d.ts +23 -0
  61. package/dist/server/app-origin-config.js +43 -0
  62. package/dist/server/auth.js +2 -1
  63. package/dist/server/core-routes-plugin.js +3 -3
  64. package/dist/server/credential-provider.js +7 -8
  65. package/dist/server/embed-session.js +2 -1
  66. package/dist/server/identity-sso.js +6 -2
  67. package/dist/server/index.d.ts +1 -0
  68. package/dist/server/index.js +1 -0
  69. package/dist/server/oauth-public-origin.js +10 -14
  70. package/dist/server/oauth-return-url.js +5 -9
  71. package/dist/server/self-dispatch.js +5 -2
  72. package/dist/server/ssr-handler.js +2 -0
  73. package/dist/server/workspace-provider-oauth.js +2 -4
  74. package/dist/sharing/actions/create-agent-resource-link.js +2 -2
  75. package/dist/usage/metrics-store.js +2 -1
  76. package/package.json +1 -1
@@ -0,0 +1,100 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * App identity.
4
+ *
5
+ * Three fields, not one, because the eight environment keys that spell "which
6
+ * app is this" were never all the same question:
7
+ *
8
+ * - `id` is this deployment's own identity — data programs, onboarding, the
9
+ * CLI, and agent model defaults scope by it.
10
+ * - `workspaceId` is the identity a workspace deploy assigns. `vault_grants`
11
+ * rows are written with it, so credential scoping must prefer it over `id`
12
+ * or an app would look up grants under a name nobody granted.
13
+ * - `name` is the user-facing display name ("Acme"), used in transactional
14
+ * emails and page titles. It is not an identifier, and only leaks into
15
+ * identity resolution as a last-resort fallback in readers that had nothing
16
+ * better.
17
+ *
18
+ * Collapsing these into one field is the tempting simplification and it is
19
+ * wrong: it would silently repoint credential grant lookups.
20
+ *
21
+ * None of them has a default. `credentialProvider` denies access when no app
22
+ * identity is configured, and a default of `"app"` would turn that denial into
23
+ * a lookup scoped to an app literally named `app`. Readers that want a
24
+ * placeholder apply it themselves, where it is visible.
25
+ */
26
+ export const appConfig = z.object({
27
+ id: z
28
+ .string()
29
+ .min(1)
30
+ .optional()
31
+ .meta({
32
+ env: ["AGENT_NATIVE_APP_ID", "APP_ID"],
33
+ doc: "Stable identity of this app deployment.",
34
+ }),
35
+ workspaceId: z
36
+ .string()
37
+ .min(1)
38
+ .optional()
39
+ .meta({
40
+ env: [
41
+ "AGENT_NATIVE_WORKSPACE_APP_ID",
42
+ "VITE_AGENT_NATIVE_WORKSPACE_APP_ID",
43
+ ],
44
+ doc: "Identity assigned by a workspace deploy. Credential grants are scoped to this.",
45
+ }),
46
+ name: z
47
+ .string()
48
+ .min(1)
49
+ .optional()
50
+ .meta({
51
+ env: ["APP_NAME"],
52
+ doc: "User-facing display name of this app.",
53
+ }),
54
+ /**
55
+ * The app's canonical public URL — what a user sees in a share link, an
56
+ * email, or a JWT issuer claim.
57
+ *
58
+ * This is NOT "where does this running deployment answer". On a deploy
59
+ * preview those differ, and conflating them is what sent background work to
60
+ * production from a preview twice. Self-address is derived by
61
+ * `resolveSelfDispatchBaseUrl()`, which consults the platform's own deploy
62
+ * URLs first and falls back to this; it is deliberately not settable here.
63
+ *
64
+ * Not `.url()`-validated on purpose. This value is read by self-dispatch,
65
+ * SSR, and OAuth, so a malformed one would make every `getAppConfig()` call
66
+ * in the process throw rather than fail in the one place that cares.
67
+ * Consumers that need a parseable URL validate it themselves and say what
68
+ * broke — `onboarding-html.ts` already reports "invalid app URL" with the
69
+ * feature named, which is a better error than a zod path.
70
+ */
71
+ url: z
72
+ .string()
73
+ .min(1)
74
+ .optional()
75
+ .meta({
76
+ env: [
77
+ "APP_URL",
78
+ "VITE_APP_URL",
79
+ "BETTER_AUTH_URL",
80
+ "VITE_BETTER_AUTH_URL",
81
+ ],
82
+ doc: "Canonical public URL of this app, used for user-facing links.",
83
+ }),
84
+ packageName: z
85
+ .string()
86
+ .min(1)
87
+ .optional()
88
+ .meta({
89
+ env: ["npm_package_name"],
90
+ doc: "Package name of the running app, as npm sets it for a script.",
91
+ }),
92
+ template: z
93
+ .string()
94
+ .min(1)
95
+ .optional()
96
+ .meta({
97
+ env: ["VITE_AGENT_NATIVE_TEMPLATE"],
98
+ doc: "First-party template this app was generated from.",
99
+ }),
100
+ });
@@ -0,0 +1,26 @@
1
+ import type { ZodType } from "zod";
2
+ /**
3
+ * Reflection over the config schema, for generators.
4
+ *
5
+ * The point of declaring configuration in one schema is that the docs, the
6
+ * documented-key sets, and the hosted-deploy allow-list can be derived from it
7
+ * instead of hand-maintained in four places. This module is what makes that
8
+ * derivation possible; `scripts/sync-config-docs.ts` is its first consumer.
9
+ */
10
+ export interface ConfigFieldDescription {
11
+ /** Dotted path, e.g. `agent.engine`. */
12
+ path: string;
13
+ /** Declared environment aliases, in precedence order. Empty when none. */
14
+ env: string[];
15
+ /** Base type after modifiers are peeled off, e.g. `string`, `boolean`. */
16
+ type: string;
17
+ /** `doc` from `.meta()`, when the field declares one. */
18
+ doc?: string;
19
+ /** Declared default, already resolved if it was a factory. */
20
+ defaultValue?: unknown;
21
+ required: boolean;
22
+ }
23
+ /** Every leaf field in the schema, in declaration order. */
24
+ export declare function describeConfigFields(schema?: ZodType): ConfigFieldDescription[];
25
+ /** Every environment key the schema declares, deduplicated and sorted. */
26
+ export declare function declaredEnvKeys(schema?: ZodType): string[];
@@ -0,0 +1,61 @@
1
+ import { collectEnvAliases } from "./env-layer.js";
2
+ import { appConfigSchema } from "./schema.js";
3
+ function internals(schema) {
4
+ return schema._zod;
5
+ }
6
+ const WRAPPER_TYPES = new Set([
7
+ "optional",
8
+ "default",
9
+ "prefault",
10
+ "nullable",
11
+ "readonly",
12
+ ]);
13
+ /** Every leaf field in the schema, in declaration order. */
14
+ export function describeConfigFields(schema = appConfigSchema) {
15
+ const fields = [];
16
+ const visit = (node, path) => {
17
+ const meta = node.meta?.();
18
+ let inner = node;
19
+ let optional = false;
20
+ let defaultValue;
21
+ while (WRAPPER_TYPES.has(internals(inner).def.type)) {
22
+ const { type, innerType, defaultValue: declared } = internals(inner).def;
23
+ if (type === "optional" || type === "nullable")
24
+ optional = true;
25
+ if (type === "default" || type === "prefault") {
26
+ defaultValue = typeof declared === "function" ? declared() : declared;
27
+ optional = true;
28
+ }
29
+ if (innerType === undefined)
30
+ break;
31
+ inner = innerType;
32
+ }
33
+ const { type, shape } = internals(inner).def;
34
+ if (type === "object" && shape) {
35
+ for (const [key, child] of Object.entries(shape)) {
36
+ visit(child, [...path, key]);
37
+ }
38
+ return;
39
+ }
40
+ const env = meta?.env;
41
+ fields.push({
42
+ path: path.join("."),
43
+ env: typeof env === "string" ? [env] : Array.isArray(env) ? env : [],
44
+ type,
45
+ doc: typeof meta?.doc === "string" ? meta.doc : undefined,
46
+ defaultValue,
47
+ required: !optional,
48
+ });
49
+ };
50
+ visit(schema, []);
51
+ return fields;
52
+ }
53
+ /** Every environment key the schema declares, deduplicated and sorted. */
54
+ export function declaredEnvKeys(schema = appConfigSchema) {
55
+ const keys = new Set();
56
+ for (const alias of collectEnvAliases(schema)) {
57
+ for (const key of alias.env)
58
+ keys.add(key);
59
+ }
60
+ return [...keys].sort();
61
+ }
@@ -0,0 +1,37 @@
1
+ import type { ZodType } from "zod";
2
+ /**
3
+ * Builds the environment-variable layer of the app config.
4
+ *
5
+ * Environment variables are declared shortcuts into the schema, not a parallel
6
+ * namespace: a key exists only because some field carries `.meta({ env })`.
7
+ * This module is the one place in the framework that turns those strings into
8
+ * typed values, which is what lets every consumer read `getAppConfig()`
9
+ * instead of parsing `process.env` at its own call site.
10
+ */
11
+ /**
12
+ * A leaf field that declares one or more environment-variable aliases.
13
+ *
14
+ * `env` is ordered: the first key that is set wins. Several fields need this
15
+ * because one concept accumulated many spellings over time — app identity has
16
+ * eight — and collapsing them means declaring the precedence once here instead
17
+ * of rebuilding a slightly different chain at each call site.
18
+ */
19
+ export interface EnvAlias {
20
+ path: string[];
21
+ env: string[];
22
+ type: string;
23
+ }
24
+ /** Every leaf in `schema` that declares an environment-variable alias. */
25
+ export declare function collectEnvAliases(schema: ZodType): EnvAlias[];
26
+ /**
27
+ * Reads every declared alias out of `env` into a partial config object.
28
+ *
29
+ * A key that is unset, empty, or only whitespace is treated as absent. Several
30
+ * hosting platforms surface an unset variable as `""`, and every reader this
31
+ * replaces used `?.trim() ||` — so a blank value has always meant "fall through
32
+ * to the next source", never "the configured value is blank".
33
+ *
34
+ * When a field declares several aliases, the first one with a value wins and
35
+ * the rest are not consulted.
36
+ */
37
+ export declare function readEnvConfigLayer(schema: ZodType, env: Record<string, string | undefined>): Record<string, unknown>;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Zod exposes no public API for peeling `.optional()` / `.default()` back off a
3
+ * field, and we need the declared kind underneath to know how to parse a string
4
+ * into it. `_zod.def` is that introspection surface; a zod major upgrade is the
5
+ * moment to re-check this file.
6
+ */
7
+ function internals(schema) {
8
+ return schema._zod;
9
+ }
10
+ const WRAPPER_TYPES = new Set([
11
+ "optional",
12
+ "default",
13
+ "prefault",
14
+ "nullable",
15
+ "readonly",
16
+ ]);
17
+ const MAX_WRAPPER_DEPTH = 10;
18
+ function unwrap(schema, path) {
19
+ let node = schema;
20
+ for (let depth = 0; depth < MAX_WRAPPER_DEPTH; depth += 1) {
21
+ const { type, innerType } = internals(node).def;
22
+ if (!WRAPPER_TYPES.has(type) || innerType === undefined)
23
+ return node;
24
+ node = innerType;
25
+ }
26
+ throw new Error(`Config field "${path.join(".")}" wraps more than ${MAX_WRAPPER_DEPTH} modifiers deep`);
27
+ }
28
+ function readEnvMeta(node) {
29
+ const meta = node.meta?.();
30
+ const env = meta?.env;
31
+ const keys = typeof env === "string" ? [env] : Array.isArray(env) ? env : [];
32
+ const valid = keys.filter((key) => typeof key === "string" && key.length > 0);
33
+ return valid.length > 0 ? valid : undefined;
34
+ }
35
+ // Walking the schema is pure and the schema is a module constant, so the walk
36
+ // happens once per schema rather than on every config read.
37
+ const aliasCache = new WeakMap();
38
+ /** Every leaf in `schema` that declares an environment-variable alias. */
39
+ export function collectEnvAliases(schema) {
40
+ const cached = aliasCache.get(schema);
41
+ if (cached)
42
+ return cached;
43
+ const aliases = [];
44
+ const visit = (node, path) => {
45
+ const env = readEnvMeta(node);
46
+ const base = unwrap(node, path);
47
+ const { type, shape } = internals(base).def;
48
+ if (type === "object" && shape) {
49
+ if (env) {
50
+ throw new Error(`Config group "${path.join(".")}" declares env "${env.join(", ")}". Only leaf fields can carry an environment alias.`);
51
+ }
52
+ for (const [key, child] of Object.entries(shape)) {
53
+ visit(child, [...path, key]);
54
+ }
55
+ return;
56
+ }
57
+ if (env)
58
+ aliases.push({ path, env, type });
59
+ };
60
+ visit(schema, []);
61
+ aliasCache.set(schema, aliases);
62
+ return aliases;
63
+ }
64
+ const TRUE_VALUES = new Set(["1", "true", "yes", "on"]);
65
+ const FALSE_VALUES = new Set(["0", "false", "no", "off"]);
66
+ function parseEnvValue(raw, key, alias) {
67
+ switch (alias.type) {
68
+ case "string":
69
+ case "enum":
70
+ case "literal":
71
+ // Every hand-rolled reader this replaces trimmed its value. Keeping that
72
+ // means a stray trailing space in a deploy UI does not become part of an
73
+ // app id or a URL.
74
+ return raw.trim();
75
+ case "boolean": {
76
+ const normalized = raw.trim().toLowerCase();
77
+ if (TRUE_VALUES.has(normalized))
78
+ return true;
79
+ if (FALSE_VALUES.has(normalized))
80
+ return false;
81
+ throw new Error(`${key} must be one of ${[...TRUE_VALUES, ...FALSE_VALUES].join(", ")}, got "${raw}"`);
82
+ }
83
+ case "number":
84
+ case "int": {
85
+ const value = Number(raw.trim());
86
+ if (!Number.isFinite(value)) {
87
+ throw new Error(`${key} must be a number, got "${raw}"`);
88
+ }
89
+ return value;
90
+ }
91
+ case "array":
92
+ // Comma-separated, which is what every hand-rolled list reader in core
93
+ // already used. Blank entries are dropped so a trailing comma is not an
94
+ // empty allow-list entry.
95
+ return raw
96
+ .split(",")
97
+ .map((entry) => entry.trim())
98
+ .filter(Boolean);
99
+ default:
100
+ throw new Error(`Config field "${alias.path.join(".")}" declares env "${key}" but its type "${alias.type}" has no environment parser. Add one here or drop the alias.`);
101
+ }
102
+ }
103
+ function assign(target, path, value) {
104
+ let node = target;
105
+ for (const key of path.slice(0, -1)) {
106
+ const existing = node[key];
107
+ if (existing === undefined)
108
+ node[key] = {};
109
+ node = node[key];
110
+ }
111
+ node[path[path.length - 1]] = value;
112
+ }
113
+ /**
114
+ * Reads every declared alias out of `env` into a partial config object.
115
+ *
116
+ * A key that is unset, empty, or only whitespace is treated as absent. Several
117
+ * hosting platforms surface an unset variable as `""`, and every reader this
118
+ * replaces used `?.trim() ||` — so a blank value has always meant "fall through
119
+ * to the next source", never "the configured value is blank".
120
+ *
121
+ * When a field declares several aliases, the first one with a value wins and
122
+ * the rest are not consulted.
123
+ */
124
+ export function readEnvConfigLayer(schema, env) {
125
+ const layer = {};
126
+ for (const alias of collectEnvAliases(schema)) {
127
+ for (const key of alias.env) {
128
+ const raw = env[key];
129
+ if (raw === undefined || raw.trim() === "")
130
+ continue;
131
+ assign(layer, alias.path, parseEnvValue(raw, key, alias));
132
+ break;
133
+ }
134
+ }
135
+ return layer;
136
+ }
@@ -0,0 +1,2 @@
1
+ export { defineAppConfig, getAppConfig, resetAppConfigForTests, } from "./store.js";
2
+ export { appConfigSchema, type AppConfig, type AppConfigInput, } from "./schema.js";
@@ -0,0 +1,2 @@
1
+ export { defineAppConfig, getAppConfig, resetAppConfigForTests, } from "./store.js";
2
+ export { appConfigSchema, } from "./schema.js";
@@ -0,0 +1,5 @@
1
+ import { z } from "zod";
2
+ /** Inbound integration webhook policy. */
3
+ export declare const integrationsConfig: z.ZodObject<{
4
+ allowUnverifiedWebhooks: z.ZodDefault<z.ZodBoolean>;
5
+ }, z.core.$strip>;
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+ /** Inbound integration webhook policy. */
3
+ export const integrationsConfig = z.object({
4
+ allowUnverifiedWebhooks: z
5
+ .boolean()
6
+ .default(false)
7
+ .meta({
8
+ env: ["AGENT_NATIVE_ALLOW_UNVERIFIED_WEBHOOKS"],
9
+ doc: "Skip inbound webhook signature verification. Development only — every adapter that reads this treats it as a bypass of sender authentication.",
10
+ }),
11
+ });
@@ -0,0 +1,14 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Private blob storage.
4
+ *
5
+ * `provider` names which registered provider is active. Leaving it unset keeps
6
+ * the historical rule — the first registered provider reporting itself
7
+ * configured — which is decided by module import order, so an app cannot state
8
+ * its choice, only hope its provider registered first. Set it explicitly
9
+ * whenever more than one provider can be configured in the same deployment.
10
+ */
11
+ export declare const privateBlobConfig: z.ZodObject<{
12
+ provider: z.ZodOptional<z.ZodString>;
13
+ publicUploadFallback: z.ZodDefault<z.ZodBoolean>;
14
+ }, z.core.$strip>;
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Private blob storage.
4
+ *
5
+ * `provider` names which registered provider is active. Leaving it unset keeps
6
+ * the historical rule — the first registered provider reporting itself
7
+ * configured — which is decided by module import order, so an app cannot state
8
+ * its choice, only hope its provider registered first. Set it explicitly
9
+ * whenever more than one provider can be configured in the same deployment.
10
+ */
11
+ export const privateBlobConfig = z.object({
12
+ provider: z.string().min(1).optional().meta({
13
+ doc: "Id of the registered private blob provider to use. Unset falls back to the first registered provider that reports itself configured.",
14
+ }),
15
+ publicUploadFallback: z.boolean().default(true).meta({
16
+ env: "AGENT_NATIVE_PRIVATE_BLOB_PUBLIC_UPLOAD_FALLBACK",
17
+ doc: "Store private blobs as encrypted objects in public file-upload storage when no private blob provider is configured.",
18
+ }),
19
+ });
@@ -0,0 +1,55 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The server-side configurable surface of the framework, in one schema.
4
+ *
5
+ * One object of optional per-domain subobjects. A domain adds a file next to
6
+ * this one and a single line here; nothing else has to learn about it.
7
+ *
8
+ * This object is never serialized. Client-visible configuration is a different
9
+ * object with a different lifetime — `AgentNativeConfig` in `src/config.ts`,
10
+ * resolved at build time and projected into the bundle. Anything that must not
11
+ * reach a browser belongs here, not there.
12
+ */
13
+ export declare const appConfigSchema: z.ZodObject<{
14
+ a2a: z.ZodPrefault<z.ZodObject<{
15
+ allowedOrigins: z.ZodDefault<z.ZodArray<z.ZodString>>;
16
+ allowUnsignedInternal: z.ZodDefault<z.ZodBoolean>;
17
+ }, z.core.$strip>>;
18
+ agent: z.ZodPrefault<z.ZodObject<{
19
+ engine: z.ZodOptional<z.ZodString>;
20
+ model: z.ZodOptional<z.ZodString>;
21
+ mode: z.ZodOptional<z.ZodString>;
22
+ preferBringYourOwnKey: z.ZodDefault<z.ZodBoolean>;
23
+ runSoftTimeoutMs: z.ZodOptional<z.ZodNumber>;
24
+ completedRunRetentionMs: z.ZodOptional<z.ZodNumber>;
25
+ erroredRunRetentionMs: z.ZodOptional<z.ZodNumber>;
26
+ }, z.core.$strip>>;
27
+ app: z.ZodPrefault<z.ZodObject<{
28
+ id: z.ZodOptional<z.ZodString>;
29
+ workspaceId: z.ZodOptional<z.ZodString>;
30
+ name: z.ZodOptional<z.ZodString>;
31
+ url: z.ZodOptional<z.ZodString>;
32
+ packageName: z.ZodOptional<z.ZodString>;
33
+ template: z.ZodOptional<z.ZodString>;
34
+ }, z.core.$strip>>;
35
+ integrations: z.ZodPrefault<z.ZodObject<{
36
+ allowUnverifiedWebhooks: z.ZodDefault<z.ZodBoolean>;
37
+ }, z.core.$strip>>;
38
+ privateBlob: z.ZodPrefault<z.ZodObject<{
39
+ provider: z.ZodOptional<z.ZodString>;
40
+ publicUploadFallback: z.ZodDefault<z.ZodBoolean>;
41
+ }, z.core.$strip>>;
42
+ workspace: z.ZodPrefault<z.ZodObject<{
43
+ gatewayUrl: z.ZodOptional<z.ZodString>;
44
+ oauthOrigin: z.ZodOptional<z.ZodString>;
45
+ }, z.core.$strip>>;
46
+ }, z.core.$strip>;
47
+ /**
48
+ * Accepted by `defineAppConfig`.
49
+ *
50
+ * Distinct from `AppConfig` because a field with `.default()` is optional on
51
+ * the way in and guaranteed present on the way out.
52
+ */
53
+ export type AppConfigInput = z.input<typeof appConfigSchema>;
54
+ /** Returned by `getAppConfig`, with declared defaults applied. */
55
+ export type AppConfig = z.output<typeof appConfigSchema>;
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ import { a2aConfig } from "./a2a.js";
3
+ import { agentConfig } from "./agent.js";
4
+ import { appConfig } from "./app.js";
5
+ import { integrationsConfig } from "./integrations.js";
6
+ import { privateBlobConfig } from "./private-blob.js";
7
+ import { workspaceConfig } from "./workspace.js";
8
+ /**
9
+ * The server-side configurable surface of the framework, in one schema.
10
+ *
11
+ * One object of optional per-domain subobjects. A domain adds a file next to
12
+ * this one and a single line here; nothing else has to learn about it.
13
+ *
14
+ * This object is never serialized. Client-visible configuration is a different
15
+ * object with a different lifetime — `AgentNativeConfig` in `src/config.ts`,
16
+ * resolved at build time and projected into the bundle. Anything that must not
17
+ * reach a browser belongs here, not there.
18
+ */
19
+ // Every domain wraps in `.prefault({})`, not `.optional()` or `.default({})`.
20
+ // An optional domain never materializes the defaults declared inside it, and
21
+ // `.default({})` hands back the literal `{}` without parsing it — both leave
22
+ // readers with `undefined` where the type promises a value. `.prefault` runs
23
+ // the domain schema over the empty object, so a declared default is there.
24
+ export const appConfigSchema = z.object({
25
+ a2a: a2aConfig.prefault({}),
26
+ agent: agentConfig.prefault({}),
27
+ app: appConfig.prefault({}),
28
+ integrations: integrationsConfig.prefault({}),
29
+ privateBlob: privateBlobConfig.prefault({}),
30
+ workspace: workspaceConfig.prefault({}),
31
+ });
@@ -0,0 +1,42 @@
1
+ import { type AppConfig, type AppConfigInput } from "./schema.js";
2
+ /**
3
+ * Resolution order for app configuration, lowest opinion first.
4
+ *
5
+ * `legacy` is where the bespoke `configure*` / `set*` setters this schema
6
+ * replaces write their values, so a deprecated call still works and still has
7
+ * a stated position rather than whichever `if` happens to run first.
8
+ */
9
+ declare const LAYER_ORDER: readonly ["env", "legacy", "app"];
10
+ export type AppConfigLayer = (typeof LAYER_ORDER)[number];
11
+ /**
12
+ * Writes one layer of the ladder. Framework-internal: the deprecated setters
13
+ * use it to keep working without reintroducing a second namespace.
14
+ */
15
+ export declare function setAppConfigLayer(layer: AppConfigLayer, config: AppConfigInput): void;
16
+ /**
17
+ * Sets this app's server configuration.
18
+ *
19
+ * Call it from a server plugin. Values given here beat any environment
20
+ * variable aliased to the same field. Calling it more than once merges, so an
21
+ * app can split its configuration by domain across plugin files.
22
+ */
23
+ export declare function defineAppConfig(config: AppConfigInput): void;
24
+ /**
25
+ * The resolved configuration, with declared defaults applied.
26
+ *
27
+ * The parsed result is cached, but the env layer is rebuilt each call and the
28
+ * cache is dropped when it differs. Re-reading a handful of declared keys is
29
+ * cheap next to re-parsing the schema, and caching the env read outright would
30
+ * mean whichever code path ran first froze the configuration for the process —
31
+ * a stale value nobody can see, which is the failure this module exists to
32
+ * remove.
33
+ */
34
+ export declare function getAppConfig(): AppConfig;
35
+ /**
36
+ * Drops every layer and the resolved cache.
37
+ *
38
+ * The resolved object is cached on first read, so a test that mutates
39
+ * `process.env` between cases has to call this to be seen.
40
+ */
41
+ export declare function resetAppConfigForTests(): void;
42
+ export {};
@@ -0,0 +1,104 @@
1
+ import { readEnvConfigLayer } from "./env-layer.js";
2
+ import { appConfigSchema, } from "./schema.js";
3
+ /**
4
+ * Resolution order for app configuration, lowest opinion first.
5
+ *
6
+ * `legacy` is where the bespoke `configure*` / `set*` setters this schema
7
+ * replaces write their values, so a deprecated call still works and still has
8
+ * a stated position rather than whichever `if` happens to run first.
9
+ */
10
+ const LAYER_ORDER = ["env", "legacy", "app"];
11
+ // Same reason the provider registries do this: core can be loaded more than
12
+ // once in a dev server or a dual-format build, and a config set by a plugin
13
+ // has to be visible to a reader that resolved a different copy of the module.
14
+ const globals = globalThis;
15
+ const state = (globals.__agentNativeAppConfig ??= {
16
+ layers: {},
17
+ });
18
+ function isPlainObject(value) {
19
+ return (typeof value === "object" &&
20
+ value !== null &&
21
+ Object.getPrototypeOf(value) === Object.prototype);
22
+ }
23
+ /**
24
+ * Merges `override` onto `base`, recursing into domain subobjects so two
25
+ * layers can each set a different field of the same domain. Functions, arrays,
26
+ * and scalars replace rather than merge.
27
+ */
28
+ function mergeLayers(base, override) {
29
+ const result = { ...base };
30
+ for (const [key, value] of Object.entries(override)) {
31
+ if (value === undefined)
32
+ continue;
33
+ const existing = result[key];
34
+ result[key] =
35
+ isPlainObject(existing) && isPlainObject(value)
36
+ ? mergeLayers(existing, value)
37
+ : value;
38
+ }
39
+ return result;
40
+ }
41
+ function resolve(envLayer) {
42
+ state.layers.env = envLayer;
43
+ let merged = {};
44
+ for (const layer of LAYER_ORDER) {
45
+ const value = state.layers[layer];
46
+ if (value)
47
+ merged = mergeLayers(merged, value);
48
+ }
49
+ return appConfigSchema.parse(merged);
50
+ }
51
+ /**
52
+ * Writes one layer of the ladder. Framework-internal: the deprecated setters
53
+ * use it to keep working without reintroducing a second namespace.
54
+ */
55
+ export function setAppConfigLayer(layer, config) {
56
+ const existing = state.layers[layer];
57
+ const next = existing
58
+ ? mergeLayers(existing, config)
59
+ : config;
60
+ // Validate where the value is set, so a bad value names the call site that
61
+ // set it instead of whichever unrelated read happened to run first.
62
+ appConfigSchema.parse(next);
63
+ state.layers[layer] = next;
64
+ state.resolved = undefined;
65
+ }
66
+ /**
67
+ * Sets this app's server configuration.
68
+ *
69
+ * Call it from a server plugin. Values given here beat any environment
70
+ * variable aliased to the same field. Calling it more than once merges, so an
71
+ * app can split its configuration by domain across plugin files.
72
+ */
73
+ export function defineAppConfig(config) {
74
+ setAppConfigLayer("app", config);
75
+ }
76
+ /**
77
+ * The resolved configuration, with declared defaults applied.
78
+ *
79
+ * The parsed result is cached, but the env layer is rebuilt each call and the
80
+ * cache is dropped when it differs. Re-reading a handful of declared keys is
81
+ * cheap next to re-parsing the schema, and caching the env read outright would
82
+ * mean whichever code path ran first froze the configuration for the process —
83
+ * a stale value nobody can see, which is the failure this module exists to
84
+ * remove.
85
+ */
86
+ export function getAppConfig() {
87
+ const envLayer = readEnvConfigLayer(appConfigSchema, process.env);
88
+ const signature = JSON.stringify(envLayer);
89
+ if (state.resolved && state.envSignature === signature)
90
+ return state.resolved;
91
+ state.envSignature = signature;
92
+ return (state.resolved = resolve(envLayer));
93
+ }
94
+ /**
95
+ * Drops every layer and the resolved cache.
96
+ *
97
+ * The resolved object is cached on first read, so a test that mutates
98
+ * `process.env` between cases has to call this to be seen.
99
+ */
100
+ export function resetAppConfigForTests() {
101
+ state.layers = {};
102
+ state.resolved = undefined;
103
+ state.envSignature = undefined;
104
+ }