@agent-native/core 0.145.1 → 0.145.3
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/corpus/README.md +1 -1
- package/corpus/templates/brain/server/lib/capture-sanitization.ts +6 -2
- package/corpus/templates/clips/changelog/2026-08-07-full-screen-recordings-on-a-multi-monitor-mac-now-let-you-pi.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/styles.css +13 -4
- package/corpus/templates/clips/desktop/src/app.tsx +38 -1
- package/corpus/templates/clips/desktop/src/components/MediaDeviceRow.tsx +123 -104
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +165 -2
- package/corpus/templates/clips/desktop/src/main.tsx +3 -0
- package/corpus/templates/clips/desktop/src/overlays/monitor-picker.tsx +70 -0
- package/corpus/templates/clips/desktop/src/styles.css +70 -11
- package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +2 -1
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +163 -1
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +6 -1
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +141 -22
- package/corpus/templates/clips/desktop/src-tauri/src/state.rs +27 -1
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +12 -1
- package/dist/agent/engine/index.d.ts +1 -1
- package/dist/agent/engine/index.js +1 -1
- package/dist/agent/engine/registry.d.ts +15 -0
- package/dist/agent/engine/registry.js +39 -10
- package/dist/agent/production-agent.d.ts +40 -1
- package/dist/agent/production-agent.js +81 -40
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/webhook-handler.d.ts +3 -2
- package/dist/integrations/webhook-handler.js +8 -38
- package/dist/observability/routes.d.ts +3 -3
- package/dist/server/agent-chat-plugin.js +67 -33
- package/dist/server/complete-text.js +14 -7
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/poll.d.ts +11 -0
- package/dist/server/poll.js +7 -4
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/realtime-token.js +8 -0
- package/dist/server/request-context.d.ts +7 -1
- package/dist/server/short-lived-token.d.ts +8 -0
- package/dist/server/short-lived-token.js +12 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/docs/content/environment-variables.mdx +6 -0
- package/package.json +3 -3
|
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
-
error?: undefined;
|
|
45
44
|
summary: import("./types.js").TraceSummary;
|
|
46
45
|
spans: import("./types.js").TraceSpan[];
|
|
47
46
|
id?: undefined;
|
|
47
|
+
error?: undefined;
|
|
48
48
|
ok?: undefined;
|
|
49
49
|
} | {
|
|
50
|
-
error?: undefined;
|
|
51
50
|
summary?: undefined;
|
|
52
51
|
spans?: undefined;
|
|
53
52
|
id: string;
|
|
53
|
+
error?: undefined;
|
|
54
54
|
ok?: undefined;
|
|
55
55
|
} | {
|
|
56
56
|
summary?: undefined;
|
|
@@ -59,9 +59,9 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
59
59
|
error: any;
|
|
60
60
|
ok?: undefined;
|
|
61
61
|
} | {
|
|
62
|
-
error?: undefined;
|
|
63
62
|
summary?: undefined;
|
|
64
63
|
spans?: undefined;
|
|
65
64
|
id?: undefined;
|
|
65
|
+
error?: undefined;
|
|
66
66
|
ok: boolean;
|
|
67
67
|
}>>;
|
|
@@ -11,6 +11,7 @@ import { DEFAULT_ANTHROPIC_MODEL } from "../agent/default-model.js";
|
|
|
11
11
|
import { AGENT_CHAT_BACKGROUND_RUN_FIELD, AGENT_CHAT_PROCESS_RUN_PATH, backgroundRunMarkerExpectsBackgroundRuntime, isInBackgroundFunctionRuntime, prepareProcessRunRequest, } from "../agent/durable-background.js";
|
|
12
12
|
import { resolveEngine, createAnthropicEngine, getStoredModelForEngine, normalizeModelForEngine, resolveDelegatedRunModel, getAgentEngineEntry, isAgentEnginePackageInstalled, isStoredEngineUsableForRequest, listAgentEngines, registerBuiltinEngines, } from "../agent/engine/index.js";
|
|
13
13
|
import { SYSTEM_PROMPT_CACHE_SPLIT } from "../agent/engine/prompt-cache.js";
|
|
14
|
+
import { PROVIDER_TO_ENV } from "../agent/engine/provider-env-vars.js";
|
|
14
15
|
import { createProductionAgentHandler, actionsToEngineTools, executeAgentToolCall, toolCallCacheKey, getActiveRunForThreadAsync, abortRunDurably, abortTurnDurably, subscribeToRun, } from "../agent/production-agent.js";
|
|
15
16
|
import { callerHasRunAccess, callerHasThreadAccess, } from "../agent/run-ownership.js";
|
|
16
17
|
import { markTurnAborted, readBackgroundRunClaim } from "../agent/run-store.js";
|
|
@@ -1240,8 +1241,12 @@ export function createAgentChatPlugin(options) {
|
|
|
1240
1241
|
}
|
|
1241
1242
|
// Use the SAME agent setup as the interactive chat — identical tools,
|
|
1242
1243
|
// prompt, and capabilities. The A2A agent IS the app's agent.
|
|
1243
|
-
const {
|
|
1244
|
-
const ownerApiKey = await
|
|
1244
|
+
const { resolveOwnerEngineApiKey } = await import("../agent/production-agent.js");
|
|
1245
|
+
const { apiKey: ownerApiKey, apiKeyEnvVar: ownerApiKeyEnvVar } = await resolveOwnerEngineApiKey({
|
|
1246
|
+
engineOption: options?.engine,
|
|
1247
|
+
ownerEmail: userEmail,
|
|
1248
|
+
anthropicFallback: options?.apiKey,
|
|
1249
|
+
});
|
|
1245
1250
|
// A2A runs are reconstructed in a fresh processor request, so they
|
|
1246
1251
|
// do not pass through the interactive handler's prepareRun hook.
|
|
1247
1252
|
// Seed the same mutable run context before resolving the engine and
|
|
@@ -1254,6 +1259,7 @@ export function createAgentChatPlugin(options) {
|
|
|
1254
1259
|
if (a2aRunContext) {
|
|
1255
1260
|
a2aRunContext.owner = userEmail;
|
|
1256
1261
|
a2aRunContext.userApiKey = ownerApiKey;
|
|
1262
|
+
a2aRunContext.userApiKeyEnvVar = ownerApiKeyEnvVar;
|
|
1257
1263
|
// The async processor restores the original request origin from
|
|
1258
1264
|
// task metadata. Only derive a fallback for synchronous A2A calls
|
|
1259
1265
|
// where the inbound event is still the caller request.
|
|
@@ -1274,7 +1280,8 @@ export function createAgentChatPlugin(options) {
|
|
|
1274
1280
|
}
|
|
1275
1281
|
const a2aEngine = await resolveEngine({
|
|
1276
1282
|
engineOption: options?.engine,
|
|
1277
|
-
apiKey: ownerApiKey
|
|
1283
|
+
apiKey: ownerApiKey,
|
|
1284
|
+
apiKeyEnvVar: ownerApiKeyEnvVar,
|
|
1278
1285
|
appId: options?.appId,
|
|
1279
1286
|
});
|
|
1280
1287
|
const devActive = isDevMode();
|
|
@@ -1684,13 +1691,27 @@ export function createAgentChatPlugin(options) {
|
|
|
1684
1691
|
askAgent: async (message) => {
|
|
1685
1692
|
const ownerEmail = getRequestUserEmail();
|
|
1686
1693
|
const mcpRunId = crypto.randomUUID();
|
|
1687
|
-
const {
|
|
1688
|
-
const ownerApiKey =
|
|
1689
|
-
|
|
1690
|
-
|
|
1694
|
+
const { resolveOwnerEngineApiKey } = await import("../agent/production-agent.js");
|
|
1695
|
+
const ownerApiKey = await resolveOwnerEngineApiKey({
|
|
1696
|
+
engineOption: options?.engine,
|
|
1697
|
+
ownerEmail,
|
|
1698
|
+
anthropicFallback: options?.apiKey,
|
|
1699
|
+
});
|
|
1700
|
+
// `ask_app` runs outside the interactive handler, so nothing seeds
|
|
1701
|
+
// the run context for it — and `onEngineResolved` never fires on
|
|
1702
|
+
// this path either. Without the resolved key and its provenance
|
|
1703
|
+
// here, an agent-team sub-agent spawned from an MCP run falls back
|
|
1704
|
+
// to the plugin host key while the parent bills the owner's BYO
|
|
1705
|
+
// credential. Same reason the A2A branch above seeds it.
|
|
1706
|
+
const mcpRunContext = ensureRequestRunContext();
|
|
1707
|
+
if (mcpRunContext) {
|
|
1708
|
+
mcpRunContext.userApiKey = ownerApiKey.apiKey;
|
|
1709
|
+
mcpRunContext.userApiKeyEnvVar = ownerApiKey.apiKeyEnvVar;
|
|
1710
|
+
}
|
|
1691
1711
|
const mcpEngine = await resolveEngine({
|
|
1692
1712
|
engineOption: options?.engine,
|
|
1693
|
-
apiKey: ownerApiKey
|
|
1713
|
+
apiKey: ownerApiKey.apiKey,
|
|
1714
|
+
apiKeyEnvVar: ownerApiKey.apiKeyEnvVar,
|
|
1694
1715
|
appId: options?.appId,
|
|
1695
1716
|
});
|
|
1696
1717
|
const mcpModelCandidate = options?.model ??
|
|
@@ -2180,12 +2201,19 @@ export function createAgentChatPlugin(options) {
|
|
|
2180
2201
|
getActions: buildSubAgentActions,
|
|
2181
2202
|
getEngine: () => {
|
|
2182
2203
|
const runCtx = getRequestRunContext();
|
|
2204
|
+
// Sub-agents must inherit the parent run's resolved key so
|
|
2205
|
+
// delegations spawned by agent-teams don't silently fall back to
|
|
2206
|
+
// the platform key while the parent uses BYO credentials. This
|
|
2207
|
+
// fallback engine is Anthropic, so a key the parent resolved for
|
|
2208
|
+
// another provider is not inheritable — passing it anyway sends a
|
|
2209
|
+
// live OpenAI/Gemini secret to Anthropic's endpoint.
|
|
2210
|
+
const inheritableKey = runCtx?.userApiKeyEnvVar === undefined ||
|
|
2211
|
+
runCtx.userApiKeyEnvVar === "ANTHROPIC_API_KEY"
|
|
2212
|
+
? runCtx?.userApiKey
|
|
2213
|
+
: undefined;
|
|
2183
2214
|
return (runCtx?.engine ??
|
|
2184
2215
|
createAnthropicEngine({
|
|
2185
|
-
|
|
2186
|
-
// delegations spawned by agent-teams don't silently fall back
|
|
2187
|
-
// to the platform key while the parent uses BYO credentials.
|
|
2188
|
-
apiKey: runCtx?.userApiKey ?? options?.apiKey,
|
|
2216
|
+
apiKey: inheritableKey ?? options?.apiKey,
|
|
2189
2217
|
}));
|
|
2190
2218
|
},
|
|
2191
2219
|
getModel: () => getRequestRunContext()?.model ?? resolvedModel,
|
|
@@ -2342,13 +2370,17 @@ export function createAgentChatPlugin(options) {
|
|
|
2342
2370
|
// content is what the token-saving modes strip.
|
|
2343
2371
|
const prepareRun = async (event) => {
|
|
2344
2372
|
const owner = await getOwnerFromEvent(event);
|
|
2345
|
-
const {
|
|
2346
|
-
const userApiKey = await
|
|
2373
|
+
const { resolveOwnerEngineApiKey } = await import("../agent/production-agent.js");
|
|
2374
|
+
const userApiKey = await resolveOwnerEngineApiKey({
|
|
2375
|
+
engineOption: options?.engine,
|
|
2376
|
+
ownerEmail: owner,
|
|
2377
|
+
});
|
|
2347
2378
|
const runCtx = ensureRequestRunContext();
|
|
2348
2379
|
if (runCtx) {
|
|
2349
2380
|
runCtx.requestOrigin = getOrigin(event);
|
|
2350
2381
|
runCtx.owner = owner;
|
|
2351
|
-
runCtx.userApiKey = userApiKey;
|
|
2382
|
+
runCtx.userApiKey = userApiKey.apiKey;
|
|
2383
|
+
runCtx.userApiKeyEnvVar = userApiKey.apiKeyEnvVar;
|
|
2352
2384
|
}
|
|
2353
2385
|
const extra = await resolveExtraContext(event, owner);
|
|
2354
2386
|
return { owner, extra };
|
|
@@ -2938,21 +2970,28 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
2938
2970
|
resolveConfig: async ({ payload, ownerEmail, orgId: _orgId }) => {
|
|
2939
2971
|
// Resolve the owner's API key so BYO-key sub-agents use the
|
|
2940
2972
|
// same credentials as the parent chat.
|
|
2941
|
-
let
|
|
2973
|
+
let resolvedKey = {
|
|
2974
|
+
apiKey: undefined,
|
|
2975
|
+
apiKeyEnvVar: undefined,
|
|
2976
|
+
};
|
|
2942
2977
|
try {
|
|
2943
|
-
const {
|
|
2944
|
-
|
|
2945
|
-
|
|
2978
|
+
const { resolveOwnerEngineApiKey } = await import("../agent/production-agent.js");
|
|
2979
|
+
resolvedKey = await resolveOwnerEngineApiKey({
|
|
2980
|
+
engineOption: options?.engine,
|
|
2981
|
+
ownerEmail,
|
|
2982
|
+
anthropicFallback: options?.apiKey,
|
|
2983
|
+
});
|
|
2946
2984
|
}
|
|
2947
2985
|
catch {
|
|
2948
|
-
|
|
2986
|
+
resolvedKey = { apiKey: undefined, apiKeyEnvVar: undefined };
|
|
2949
2987
|
}
|
|
2950
2988
|
// Use the same resolveEngine path as the A2A and MCP
|
|
2951
2989
|
// processors so Builder-gateway/OpenAI users get their
|
|
2952
2990
|
// configured engine instead of always hitting the Anthropic SDK.
|
|
2953
2991
|
const engine = await resolveEngine({
|
|
2954
2992
|
engineOption: options?.engine,
|
|
2955
|
-
apiKey: apiKey
|
|
2993
|
+
apiKey: resolvedKey.apiKey,
|
|
2994
|
+
apiKeyEnvVar: resolvedKey.apiKeyEnvVar,
|
|
2956
2995
|
appId: options?.appId,
|
|
2957
2996
|
});
|
|
2958
2997
|
const modelCandidate = payload.model ??
|
|
@@ -3148,15 +3187,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
3148
3187
|
setResponseStatus(event, 401);
|
|
3149
3188
|
return { error: "Authentication required" };
|
|
3150
3189
|
}
|
|
3151
|
-
const
|
|
3152
|
-
anthropic: "ANTHROPIC_API_KEY",
|
|
3153
|
-
openai: "OPENAI_API_KEY",
|
|
3154
|
-
google: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
3155
|
-
groq: "GROQ_API_KEY",
|
|
3156
|
-
mistral: "MISTRAL_API_KEY",
|
|
3157
|
-
cohere: "COHERE_API_KEY",
|
|
3158
|
-
};
|
|
3159
|
-
const secretKey = providerToEnv[provider] ?? `${provider.toUpperCase()}_API_KEY`;
|
|
3190
|
+
const secretKey = PROVIDER_TO_ENV[provider] ?? `${provider.toUpperCase()}_API_KEY`;
|
|
3160
3191
|
try {
|
|
3161
3192
|
const { writeAppSecret } = await import("../secrets/storage.js");
|
|
3162
3193
|
await writeAppSecret({
|
|
@@ -3686,9 +3717,12 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
3686
3717
|
.trim();
|
|
3687
3718
|
// Mirror the chat-run resolution so BYO-key users have title
|
|
3688
3719
|
// generation billed to their own key instead of the platform key.
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3720
|
+
// This request goes straight to Anthropic, so it needs the owner's
|
|
3721
|
+
// Anthropic key specifically — the active engine may be another
|
|
3722
|
+
// provider, whose key must never be sent here. Owners without one
|
|
3723
|
+
// get the truncated title.
|
|
3724
|
+
const { getOwnerApiKeyForEngine } = await import("../agent/production-agent.js");
|
|
3725
|
+
const { apiKey } = await getOwnerApiKeyForEngine("anthropic", ownerEmail);
|
|
3692
3726
|
if (!apiKey) {
|
|
3693
3727
|
// Fallback: truncate the message
|
|
3694
3728
|
return { title: cleanMessage.trim().slice(0, 60) };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getStoredModelForEngine, normalizeModelForEngine, registerBuiltinEngines, resolveEngine, } from "../agent/engine/index.js";
|
|
2
2
|
import { EngineError } from "../agent/engine/types.js";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveOwnerEngineApiKey, } from "../agent/production-agent.js";
|
|
4
4
|
import { getRequestUserEmail } from "./request-context.js";
|
|
5
5
|
function normalizeCompleteTextMessages(messages, input) {
|
|
6
6
|
const normalized = [];
|
|
@@ -56,14 +56,20 @@ function createCompletionAbortSignal(signal, timeoutMs) {
|
|
|
56
56
|
},
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
|
-
async function resolveCompletionApiKey(
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
async function resolveCompletionApiKey(options) {
|
|
60
|
+
// A caller-supplied key is opaque: `CompleteTextOptions.apiKey` names no
|
|
61
|
+
// provider, so it keeps the caller's contract and is passed through untagged.
|
|
62
|
+
if (options.apiKey) {
|
|
63
|
+
return { apiKey: options.apiKey, apiKeyEnvVar: undefined };
|
|
64
|
+
}
|
|
62
65
|
try {
|
|
63
|
-
return await
|
|
66
|
+
return await resolveOwnerEngineApiKey({
|
|
67
|
+
engineOption: options.engine,
|
|
68
|
+
ownerEmail: getRequestUserEmail(),
|
|
69
|
+
});
|
|
64
70
|
}
|
|
65
71
|
catch {
|
|
66
|
-
return undefined;
|
|
72
|
+
return { apiKey: undefined, apiKeyEnvVar: undefined };
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
75
|
/**
|
|
@@ -75,10 +81,11 @@ async function resolveCompletionApiKey(explicitApiKey) {
|
|
|
75
81
|
*/
|
|
76
82
|
export async function completeText(options) {
|
|
77
83
|
registerBuiltinEngines();
|
|
78
|
-
const apiKey = await resolveCompletionApiKey(options
|
|
84
|
+
const { apiKey, apiKeyEnvVar } = await resolveCompletionApiKey(options);
|
|
79
85
|
const engine = await resolveEngine({
|
|
80
86
|
engineOption: options.engine,
|
|
81
87
|
apiKey,
|
|
88
|
+
apiKeyEnvVar,
|
|
82
89
|
model: options.model,
|
|
83
90
|
appId: options.appId,
|
|
84
91
|
});
|
package/dist/server/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export { assertCurrentRequestUserIsOrgAdmin, currentRequestUserIsOrgAdmin, } fro
|
|
|
14
14
|
export { verifyCaptcha, type CaptchaVerifyResult } from "./captcha.js";
|
|
15
15
|
export { getLocaleInitScript, parseAcceptLanguage, resolveLocaleFromRequest, type LocaleInitScriptOptions, type ResolveLocaleFromRequestOptions, type ResolvedRequestLocale, } from "../localization/server.js";
|
|
16
16
|
export { createProductionAgentHandler, type ActionEntry, type ScriptEntry, type ProductionAgentOptions, type ActionTool, type ScriptTool, type AgentMessage, type AgentChatRequest, type AgentChatEvent, type AgentChatAttachment, type AgentChatReference, type MentionProvider, type MentionProviderItem, type AgentLoopFinalResponseGuard, type AgentLoopFinalResponseGuardContext, type AgentLoopFinalResponseGuardResult, type AgentLoopToolCallSummary, type AgentLoopToolResultSummary, } from "../agent/index.js";
|
|
17
|
-
export { actionsToEngineTools, executeAgentToolCall, getOwnerActiveApiKey, runAgentLoop, type AgentToolCallExecutionResult, type ExecuteAgentToolCallOptions, } from "../agent/production-agent.js";
|
|
17
|
+
export { actionsToEngineTools, executeAgentToolCall, getOwnerActiveApiKey, getOwnerApiKeyForEngine, resolveOwnerEngineApiKey, runAgentLoop, type AgentToolCallExecutionResult, type ExecuteAgentToolCallOptions, type ResolvedOwnerApiKey, } from "../agent/production-agent.js";
|
|
18
18
|
export { mountRealtimeVoiceRoutes, realtimeVoiceSafetyIdentifier, REALTIME_VOICE_MAX_SDP_BYTES, REALTIME_VOICE_MAX_TOOL_BODY_BYTES, REALTIME_VOICE_MAX_TOOL_OUTPUT_CHARS, REALTIME_VOICE_SESSION_PATH, REALTIME_VOICE_TOOL_PATH, type MountRealtimeVoiceRoutesOptions, type RealtimeVoiceRequestContext, type RealtimeVoiceToolExecutionRequest, type RealtimeVoiceToolExecutionResult, } from "./realtime-voice.js";
|
|
19
19
|
export { getStoredModelForEngine, resolveEngine, } from "../agent/engine/index.js";
|
|
20
20
|
export { completeText, type CompleteTextMessage, type CompleteTextOptions, type CompleteTextResult, type CompleteTextUsage, } from "./complete-text.js";
|
package/dist/server/index.js
CHANGED
|
@@ -14,7 +14,7 @@ export { assertCurrentRequestUserIsOrgAdmin, currentRequestUserIsOrgAdmin, } fro
|
|
|
14
14
|
export { verifyCaptcha } from "./captcha.js";
|
|
15
15
|
export { getLocaleInitScript, parseAcceptLanguage, resolveLocaleFromRequest, } from "../localization/server.js";
|
|
16
16
|
export { createProductionAgentHandler, } from "../agent/index.js";
|
|
17
|
-
export { actionsToEngineTools, executeAgentToolCall, getOwnerActiveApiKey, runAgentLoop, } from "../agent/production-agent.js";
|
|
17
|
+
export { actionsToEngineTools, executeAgentToolCall, getOwnerActiveApiKey, getOwnerApiKeyForEngine, resolveOwnerEngineApiKey, runAgentLoop, } from "../agent/production-agent.js";
|
|
18
18
|
export { mountRealtimeVoiceRoutes, realtimeVoiceSafetyIdentifier, REALTIME_VOICE_MAX_SDP_BYTES, REALTIME_VOICE_MAX_TOOL_BODY_BYTES, REALTIME_VOICE_MAX_TOOL_OUTPUT_CHARS, REALTIME_VOICE_SESSION_PATH, REALTIME_VOICE_TOOL_PATH, } from "./realtime-voice.js";
|
|
19
19
|
export { getStoredModelForEngine, resolveEngine, } from "../agent/engine/index.js";
|
|
20
20
|
export { completeText, } from "./complete-text.js";
|
package/dist/server/poll.d.ts
CHANGED
|
@@ -114,6 +114,15 @@ export interface AppSyncStateOptions {
|
|
|
114
114
|
* Postgres only; ignored on SQLite.
|
|
115
115
|
*/
|
|
116
116
|
dbAssignedVersions?: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* TTL for a cached ALLOW decision. Defaults to 30s.
|
|
119
|
+
*
|
|
120
|
+
* `invalidateCollabAccessCache` only reaches the in-process default instance,
|
|
121
|
+
* so a gateway holding per-app instances cannot be told a share was revoked
|
|
122
|
+
* and serves its cached ALLOW until this lapses. Shorten it to bound that
|
|
123
|
+
* window, at the cost of more `can-see` round-trips per shared resource.
|
|
124
|
+
*/
|
|
125
|
+
accessAllowTtlMs?: number;
|
|
117
126
|
}
|
|
118
127
|
/**
|
|
119
128
|
* Per-app change-tracking state and read path. One instance per deployed app.
|
|
@@ -180,7 +189,9 @@ export declare class AppSyncState {
|
|
|
180
189
|
private readonly accessInFlight;
|
|
181
190
|
/** Per-resource generation bumped when shares/visibility change. */
|
|
182
191
|
private readonly accessInvalidationEpoch;
|
|
192
|
+
private readonly accessAllowTtlMs;
|
|
183
193
|
constructor(options?: AppSyncStateOptions);
|
|
194
|
+
private accessCacheTtl;
|
|
184
195
|
/**
|
|
185
196
|
* Durable-event id. Deterministic (hash of logical identity + `dedupeKey`,
|
|
186
197
|
* excluding the per-instance `version`) when the instance opts in AND a
|
package/dist/server/poll.js
CHANGED
|
@@ -153,9 +153,6 @@ function accessCacheKey(userEmail, orgId, resourceType, resourceId) {
|
|
|
153
153
|
function accessResourceKey(resourceType, resourceId) {
|
|
154
154
|
return `${resourceType}|${resourceId}`;
|
|
155
155
|
}
|
|
156
|
-
function accessCacheTtl(allowed) {
|
|
157
|
-
return allowed ? ACCESS_CACHE_TTL_MS : ACCESS_CACHE_DENY_TTL_MS;
|
|
158
|
-
}
|
|
159
156
|
function extensionTargetKey(target) {
|
|
160
157
|
if (target.owner)
|
|
161
158
|
return `owner:${target.owner}`;
|
|
@@ -291,14 +288,19 @@ export class AppSyncState {
|
|
|
291
288
|
accessInFlight = new Set();
|
|
292
289
|
/** Per-resource generation bumped when shares/visibility change. */
|
|
293
290
|
accessInvalidationEpoch = new Map();
|
|
291
|
+
accessAllowTtlMs;
|
|
294
292
|
constructor(options = {}) {
|
|
295
293
|
this.getDb = options.getDb ?? getDbExec;
|
|
296
294
|
this.isPg = options.isPostgres ?? isPostgres;
|
|
297
295
|
this.resolveAccessFn = options.resolveAccess ?? defaultResolveAccess;
|
|
298
296
|
this.deterministicEventIds = options.deterministicEventIds ?? false;
|
|
299
297
|
this.dbAssignedVersions = options.dbAssignedVersions ?? false;
|
|
298
|
+
this.accessAllowTtlMs = options.accessAllowTtlMs ?? ACCESS_CACHE_TTL_MS;
|
|
300
299
|
this.pollEmitter.setMaxListeners(0);
|
|
301
300
|
}
|
|
301
|
+
accessCacheTtl(allowed) {
|
|
302
|
+
return allowed ? this.accessAllowTtlMs : ACCESS_CACHE_DENY_TTL_MS;
|
|
303
|
+
}
|
|
302
304
|
/**
|
|
303
305
|
* Durable-event id. Deterministic (hash of logical identity + `dedupeKey`,
|
|
304
306
|
* excluding the per-instance `version`) when the instance opts in AND a
|
|
@@ -675,7 +677,8 @@ export class AppSyncState {
|
|
|
675
677
|
const key = accessCacheKey(userEmail, orgId, event.resourceType, event.resourceId);
|
|
676
678
|
const cached = this.accessCache.get(key);
|
|
677
679
|
const now = Date.now();
|
|
678
|
-
if (cached &&
|
|
680
|
+
if (cached &&
|
|
681
|
+
now - cached.checkedAt < this.accessCacheTtl(cached.allowed)) {
|
|
679
682
|
// Fresh, non-expired cache hit → trust the cached decision.
|
|
680
683
|
return cached.allowed ? "visible" : "hidden";
|
|
681
684
|
}
|
|
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
|
|
|
26
26
|
expiresAt?: undefined;
|
|
27
27
|
ttlSeconds?: undefined;
|
|
28
28
|
} | {
|
|
29
|
-
error?: undefined;
|
|
30
29
|
token: string;
|
|
31
30
|
expiresAt: string;
|
|
32
31
|
ttlSeconds: number;
|
|
32
|
+
error?: undefined;
|
|
33
33
|
}>>;
|
|
@@ -28,6 +28,13 @@ import { signRealtimeSubscribeToken } from "./short-lived-token.js";
|
|
|
28
28
|
export const REALTIME_HMAC_SECRET_ENV = "AGENT_NATIVE_REALTIME_HMAC_SECRET";
|
|
29
29
|
/** Short TTL — validated at connect; the gateway rotates over the stream. */
|
|
30
30
|
const REALTIME_TOKEN_TTL_SECONDS = 600;
|
|
31
|
+
/**
|
|
32
|
+
* Ceiling on how long one mint can be extended by gateway rotation before the
|
|
33
|
+
* client must come back through this session-gated endpoint. Without it a single
|
|
34
|
+
* mint streams forever and logout, session expiry, user deletion and org removal
|
|
35
|
+
* never reach an open stream.
|
|
36
|
+
*/
|
|
37
|
+
const REALTIME_SESSION_MAX_SECONDS = 15 * 60;
|
|
31
38
|
export function getRealtimeSigningSecret() {
|
|
32
39
|
return process.env[REALTIME_HMAC_SECRET_ENV]?.trim() || undefined;
|
|
33
40
|
}
|
|
@@ -76,6 +83,7 @@ export function createRealtimeTokenHandler() {
|
|
|
76
83
|
owner: session.email,
|
|
77
84
|
orgId: requestContext.orgId,
|
|
78
85
|
ttlSeconds: REALTIME_TOKEN_TTL_SECONDS,
|
|
86
|
+
absExp: Math.floor(Date.now() / 1000) + REALTIME_SESSION_MAX_SECONDS,
|
|
79
87
|
}, secret);
|
|
80
88
|
const expiresAt = new Date(Date.now() + REALTIME_TOKEN_TTL_SECONDS * 1000).toISOString();
|
|
81
89
|
return { token, expiresAt, ttlSeconds: REALTIME_TOKEN_TTL_SECONDS };
|
|
@@ -41,8 +41,14 @@ export interface RequestRunContext {
|
|
|
41
41
|
} | null;
|
|
42
42
|
/** Resolved owner email (set by prepareRun). */
|
|
43
43
|
owner?: string;
|
|
44
|
-
/** Owner's
|
|
44
|
+
/** Owner's API key for this run's engine (set by prepareRun). */
|
|
45
45
|
userApiKey?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Env var `userApiKey` was issued for. Anything that hands the key to a
|
|
48
|
+
* fixed provider must check this first — the owner's active engine is not
|
|
49
|
+
* always Anthropic, and an unchecked key reaches the wrong endpoint.
|
|
50
|
+
*/
|
|
51
|
+
userApiKeyEnvVar?: string;
|
|
46
52
|
/** Thread ID for the current run (set by onRunStart). */
|
|
47
53
|
threadId?: string;
|
|
48
54
|
/** Run ID for the current run (set by onRunStart). */
|
|
@@ -82,6 +82,13 @@ export interface RealtimeSubscribeClaims {
|
|
|
82
82
|
orgId?: string;
|
|
83
83
|
/** Override default TTL (seconds). */
|
|
84
84
|
ttlSeconds?: number;
|
|
85
|
+
/**
|
|
86
|
+
* Absolute unix-seconds ceiling, independent of `exp`. The gateway re-signs a
|
|
87
|
+
* stream's token every few minutes without consulting the app, so `exp` alone
|
|
88
|
+
* lets one mint be extended forever and a revoked session keeps streaming.
|
|
89
|
+
* Rotation copies this verbatim and never extends it.
|
|
90
|
+
*/
|
|
91
|
+
absExp?: number;
|
|
85
92
|
}
|
|
86
93
|
/**
|
|
87
94
|
* Result of {@link verifyRealtimeSubscribeToken}. On success it returns the
|
|
@@ -93,6 +100,7 @@ export type RealtimeVerifyResult = {
|
|
|
93
100
|
owner?: string;
|
|
94
101
|
orgId?: string;
|
|
95
102
|
exp: number;
|
|
103
|
+
absExp?: number;
|
|
96
104
|
} | {
|
|
97
105
|
ok: false;
|
|
98
106
|
reason: string;
|
|
@@ -182,6 +182,8 @@ export function signRealtimeSubscribeToken(claims, key) {
|
|
|
182
182
|
payload.owner = claims.owner;
|
|
183
183
|
if (claims.orgId)
|
|
184
184
|
payload.orgId = claims.orgId;
|
|
185
|
+
if (claims.absExp)
|
|
186
|
+
payload.absExp = claims.absExp;
|
|
185
187
|
const payloadStr = base64UrlEncode(JSON.stringify(payload));
|
|
186
188
|
return `${payloadStr}.${hmacB64(payloadStr, key)}`;
|
|
187
189
|
}
|
|
@@ -218,6 +220,15 @@ export function verifyRealtimeSubscribeToken(token, expected) {
|
|
|
218
220
|
if (claims.exp * 1000 < Date.now()) {
|
|
219
221
|
return { ok: false, reason: "expired" };
|
|
220
222
|
}
|
|
223
|
+
if (claims.absExp !== undefined) {
|
|
224
|
+
if (typeof claims.absExp !== "number") {
|
|
225
|
+
return { ok: false, reason: "bad_payload" };
|
|
226
|
+
}
|
|
227
|
+
// `<=`, not `<`: a ceiling is not valid at the instant it is reached.
|
|
228
|
+
if (claims.absExp * 1000 <= Date.now()) {
|
|
229
|
+
return { ok: false, reason: "session_expired" };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
221
232
|
if (claims.projectId !== expected.projectId) {
|
|
222
233
|
return { ok: false, reason: "wrong_project" };
|
|
223
234
|
}
|
|
@@ -227,6 +238,7 @@ export function verifyRealtimeSubscribeToken(token, expected) {
|
|
|
227
238
|
owner: claims.owner,
|
|
228
239
|
orgId: claims.orgId,
|
|
229
240
|
exp: claims.exp,
|
|
241
|
+
absExp: claims.absExp,
|
|
230
242
|
};
|
|
231
243
|
}
|
|
232
244
|
// ── Gateway access-check tokens ──────────────────────────────────────────────
|
|
@@ -40,6 +40,8 @@ deployment-specific values in the environment or scoped secret store.
|
|
|
40
40
|
| `WORKSPACE_SECRETS_ENCRYPTION_KEY` | Stable encryption key for a workspace-shared secrets vault. | The same stable key in every app that reads the vault. |
|
|
41
41
|
| `WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS` | Previous workspace key during rotation. | The old key only; remove it after migration. |
|
|
42
42
|
| `ANTHROPIC_API_KEY` | Optional deploy-level fallback for the embedded production agent. | An API key; prefer scoped credentials for multi-tenant apps. |
|
|
43
|
+
| `AGENT_ENGINE` | Explicitly selects the agent engine instead of auto-detecting from provider keys. | A registered engine name such as `anthropic`, `ai-sdk:openai`, `builder`, or `codex-cli`. |
|
|
44
|
+
| `AGENT_ENGINE_PREFER_BYO_KEY` | Prefers a provider key you set over the managed Builder gateway during auto-detection. | `true` or `1`; Builder still applies as the fallback when no provider key is set. |
|
|
43
45
|
| `AUTH_MODE` | Selects the local authentication behavior used by CLI and agent flows. | Unset or `local`; this is not a browser login bypass. |
|
|
44
46
|
| `AUTH_DISABLED` | Disables auth for local or preview-only work. | `true` or `1`; never use on a public production app. |
|
|
45
47
|
| `AUTH_MAGIC_LINK` | Controls the password-first versus magic-link sign-in path. | `0` keeps password-first sign-in; otherwise a configured email transport can enable magic links. |
|
|
@@ -95,8 +97,12 @@ specific Google API integrations; they are not required for browser sign-in.
|
|
|
95
97
|
These are deploy-level fallbacks for single-tenant or local use. Hosted,
|
|
96
98
|
multi-user apps should prefer the scoped credential and connection store.
|
|
97
99
|
|
|
100
|
+
Every provider key below selects its engine on its own: dropping one in is
|
|
101
|
+
enough, and `AGENT_ENGINE` is only needed to pick between several.
|
|
102
|
+
|
|
98
103
|
| Variable | What it controls | Supported values or format |
|
|
99
104
|
| ------------------------------ | -------------------------------------------------------- | ------------------------------------------------------- |
|
|
105
|
+
| `ANTHROPIC_API_KEY` | Claude model fallback for the built-in AI engine. | An Anthropic API key. |
|
|
100
106
|
| `OPENAI_API_KEY` | OpenAI model, realtime voice, or transcription fallback. | An OpenAI API key. |
|
|
101
107
|
| `OPENAI_BASE_URL` | OpenAI-compatible endpoint used with `OPENAI_API_KEY`. | An absolute `http(s)` URL without embedded credentials. |
|
|
102
108
|
| `GOOGLE_GENERATIVE_AI_API_KEY` | Gemini model fallback for the built-in AI engine. | A Google AI API key. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.145.
|
|
3
|
+
"version": "0.145.3",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -412,8 +412,8 @@
|
|
|
412
412
|
"y-protocols": "^1.0.7",
|
|
413
413
|
"yjs": "^13.6.31",
|
|
414
414
|
"zod": "^4.3.6",
|
|
415
|
-
"@agent-native/
|
|
416
|
-
"@agent-native/
|
|
415
|
+
"@agent-native/recap-cli": "0.5.3",
|
|
416
|
+
"@agent-native/toolkit": "^0.13.3"
|
|
417
417
|
},
|
|
418
418
|
"devDependencies": {
|
|
419
419
|
"@ai-sdk/anthropic": "^3.0.71",
|