@agent-native/core 0.145.3 → 0.145.5
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/analytics/server/plugins/agent-chat.ts +10 -8
- package/corpus/templates/assets/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +8 -3
- package/corpus/templates/clips/app/components/editor/waveform.tsx +2 -1
- package/corpus/templates/clips/app/components/library/search-bar.tsx +1 -1
- package/corpus/templates/clips/app/global.css +9 -0
- package/corpus/templates/clips/desktop/src/lib/media-capture-constraints.ts +22 -1
- package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +10 -11
- package/corpus/templates/design/actions/view-screen.ts +1 -0
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +14 -1
- package/corpus/templates/design/changelog/2026-08-07-cmd-c-now-copies-selected-text-in-the-agent-panel-instead-of.md +6 -0
- package/corpus/templates/design/changelog/2026-08-07-real-time-voice-can-now-read-the-current-selection-and-edit-.md +6 -0
- package/corpus/templates/design/server/lib/reprompt-action-guard.ts +7 -3
- package/corpus/templates/mail/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/plan/server/lib/plan-framework-tools.ts +11 -0
- package/corpus/templates/plan/server/plugins/00-mcp.ts +25 -68
- package/corpus/templates/plan/server/plugins/agent-chat.ts +8 -2
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/framework-tools.d.ts +33 -1
- package/dist/framework-tools.js +88 -4
- package/dist/mcp/build-server.d.ts +15 -0
- package/dist/mcp/build-server.js +94 -30
- package/dist/mcp/oauth-client-metadata.js +6 -2
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +6 -6
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.d.ts +3 -18
- package/dist/server/action-discovery.js +9 -65
- package/dist/server/agent-chat/action-filters-a2a.d.ts +14 -2
- package/dist/server/agent-chat/action-filters-a2a.js +1 -1
- package/dist/server/agent-chat/mcp-options.d.ts +106 -0
- package/dist/server/agent-chat/mcp-options.js +50 -0
- package/dist/server/agent-chat/plugin-options.d.ts +17 -26
- package/dist/server/agent-chat-plugin.js +21 -11
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +7 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/realtime-voice.d.ts +2 -1
- package/dist/server/realtime-voice.js +38 -56
- package/dist/server/short-lived-token.d.ts +43 -0
- package/dist/server/short-lived-token.js +74 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +2 -2
|
@@ -9,6 +9,7 @@ import { getBuilderGatewayBaseUrl, resolveBuilderCredentials, resolveSecret, } f
|
|
|
9
9
|
import { getH3App } from "./framework-request-handler.js";
|
|
10
10
|
import { runWithRequestContext } from "./request-context.js";
|
|
11
11
|
import { isSameOriginRequest } from "./request-origin.js";
|
|
12
|
+
import { signRealtimeVoiceCapability, verifyRealtimeVoiceCapability, } from "./short-lived-token.js";
|
|
12
13
|
export const REALTIME_VOICE_SESSION_PATH = "/_agent-native/realtime-voice/session";
|
|
13
14
|
export const REALTIME_VOICE_TOOL_PATH = "/_agent-native/realtime-voice/tool";
|
|
14
15
|
export const REALTIME_VOICE_MAX_SDP_BYTES = 64 * 1024;
|
|
@@ -17,8 +18,9 @@ export const REALTIME_VOICE_MAX_TOOL_OUTPUT_CHARS = 16_000;
|
|
|
17
18
|
export const REALTIME_VOICE_MAX_TOOLS = 32;
|
|
18
19
|
export const REALTIME_VOICE_MAX_TOOL_SCHEMA_BYTES = 32_000;
|
|
19
20
|
export const REALTIME_VOICE_MAX_SESSION_BYTES = 64_000;
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
/** Absolute, not sliding — a signed grant cannot be extended server-side, so
|
|
22
|
+
* this must outlast the provider's 60-minute maximum realtime session. */
|
|
23
|
+
export const REALTIME_VOICE_TOOL_GRANT_TTL_MS = 75 * 60 * 1_000;
|
|
22
24
|
export const REALTIME_VOICE_CAPABILITY_HEADER = "X-Agent-Native-Realtime-Capability";
|
|
23
25
|
const OPENAI_REALTIME_CALLS_URL = "https://api.openai.com/v1/realtime/calls";
|
|
24
26
|
const DEFAULT_MODEL = "gpt-realtime-2.1";
|
|
@@ -207,56 +209,30 @@ function packRealtimeTools(session, eligibleTools) {
|
|
|
207
209
|
}
|
|
208
210
|
return packed;
|
|
209
211
|
}
|
|
210
|
-
function mintRealtimeToolCapability() {
|
|
211
|
-
return
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
while (capabilities.size > REALTIME_VOICE_MAX_TOOL_GRANT_SESSIONS) {
|
|
219
|
-
let oldestKey;
|
|
220
|
-
let oldestExpiry = Number.POSITIVE_INFINITY;
|
|
221
|
-
for (const [key, capability] of capabilities) {
|
|
222
|
-
if (capability.expiresAt < oldestExpiry) {
|
|
223
|
-
oldestKey = key;
|
|
224
|
-
oldestExpiry = capability.expiresAt;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
if (!oldestKey)
|
|
228
|
-
break;
|
|
229
|
-
capabilities.delete(oldestKey);
|
|
230
|
-
}
|
|
212
|
+
function mintRealtimeToolCapability(auth, capability) {
|
|
213
|
+
return signRealtimeVoiceCapability({
|
|
214
|
+
userEmail: auth.userEmail,
|
|
215
|
+
...(auth.orgId ? { orgId: auth.orgId } : {}),
|
|
216
|
+
...(auth.browserTabId ? { browserTabId: auth.browserTabId } : {}),
|
|
217
|
+
toolNames: [...capability.initialNames],
|
|
218
|
+
discoveredToolNames: [...capability.names],
|
|
219
|
+
}, REALTIME_VOICE_TOOL_GRANT_TTL_MS / 1_000);
|
|
231
220
|
}
|
|
232
|
-
function
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
capabilities.set(token, {
|
|
236
|
-
userEmail: auth.userEmail.trim().toLowerCase(),
|
|
221
|
+
function resolveRealtimeToolCapability(token, auth) {
|
|
222
|
+
const verified = verifyRealtimeVoiceCapability(token, {
|
|
223
|
+
userEmail: auth.userEmail,
|
|
237
224
|
...(auth.orgId ? { orgId: auth.orgId } : {}),
|
|
238
225
|
...(auth.browserTabId ? { browserTabId: auth.browserTabId } : {}),
|
|
239
|
-
expiresAt: Date.now() + REALTIME_VOICE_TOOL_GRANT_TTL_MS,
|
|
240
|
-
initialNames: new Set(initialNames),
|
|
241
|
-
names: new Set(),
|
|
242
226
|
});
|
|
243
|
-
|
|
244
|
-
return token;
|
|
245
|
-
}
|
|
246
|
-
function resolveRealtimeToolCapability(capabilities, token, auth) {
|
|
247
|
-
cleanRealtimeToolCapabilities(capabilities);
|
|
248
|
-
if (!token)
|
|
249
|
-
return null;
|
|
250
|
-
const capability = capabilities.get(token);
|
|
251
|
-
if (!capability)
|
|
252
|
-
return null;
|
|
253
|
-
if (capability.userEmail !== auth.userEmail.trim().toLowerCase() ||
|
|
254
|
-
capability.orgId !== auth.orgId ||
|
|
255
|
-
capability.browserTabId !== auth.browserTabId) {
|
|
227
|
+
if (!verified.ok)
|
|
256
228
|
return null;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
229
|
+
return {
|
|
230
|
+
userEmail: verified.userEmail,
|
|
231
|
+
...(verified.orgId ? { orgId: verified.orgId } : {}),
|
|
232
|
+
...(verified.browserTabId ? { browserTabId: verified.browserTabId } : {}),
|
|
233
|
+
initialNames: new Set(verified.toolNames),
|
|
234
|
+
names: new Set(verified.discoveredToolNames),
|
|
235
|
+
};
|
|
260
236
|
}
|
|
261
237
|
function parseSuccessfulToolSearchNames(output) {
|
|
262
238
|
try {
|
|
@@ -302,7 +278,6 @@ function grantDiscoveredRealtimeTools(input) {
|
|
|
302
278
|
input.capability.names.add(tool.name);
|
|
303
279
|
expandedTools.push(tool);
|
|
304
280
|
}
|
|
305
|
-
input.capability.expiresAt = Date.now() + REALTIME_VOICE_TOOL_GRANT_TTL_MS;
|
|
306
281
|
return expandedTools;
|
|
307
282
|
}
|
|
308
283
|
function declaredBodyBytes(event) {
|
|
@@ -364,7 +339,7 @@ function invalidMethod(event) {
|
|
|
364
339
|
setResponseStatus(event, 405);
|
|
365
340
|
return { error: "Method not allowed" };
|
|
366
341
|
}
|
|
367
|
-
function createSessionHandler(tools,
|
|
342
|
+
function createSessionHandler(tools, options) {
|
|
368
343
|
return defineEventHandler(async (event) => {
|
|
369
344
|
if (getMethod(event) !== "POST")
|
|
370
345
|
return invalidMethod(event);
|
|
@@ -510,7 +485,10 @@ function createSessionHandler(tools, capabilities, options) {
|
|
|
510
485
|
}
|
|
511
486
|
setResponseStatus(event, upstream.status);
|
|
512
487
|
setResponseHeader(event, "Content-Type", "application/sdp");
|
|
513
|
-
setResponseHeader(event, REALTIME_VOICE_CAPABILITY_HEADER,
|
|
488
|
+
setResponseHeader(event, REALTIME_VOICE_CAPABILITY_HEADER, mintRealtimeToolCapability(auth, {
|
|
489
|
+
initialNames: new Set(packedTools.map((tool) => tool.name)),
|
|
490
|
+
names: new Set(),
|
|
491
|
+
}));
|
|
514
492
|
return answerSdp;
|
|
515
493
|
});
|
|
516
494
|
});
|
|
@@ -563,7 +541,7 @@ function normalizeExecutionResult(result) {
|
|
|
563
541
|
: {}),
|
|
564
542
|
};
|
|
565
543
|
}
|
|
566
|
-
function createToolHandler(toolsByName,
|
|
544
|
+
function createToolHandler(toolsByName, options) {
|
|
567
545
|
return defineEventHandler(async (event) => {
|
|
568
546
|
if (getMethod(event) !== "POST")
|
|
569
547
|
return invalidMethod(event);
|
|
@@ -577,7 +555,7 @@ function createToolHandler(toolsByName, capabilities, options) {
|
|
|
577
555
|
setResponseStatus(event, 401);
|
|
578
556
|
return { error: "Authentication required" };
|
|
579
557
|
}
|
|
580
|
-
const capability = resolveRealtimeToolCapability(
|
|
558
|
+
const capability = resolveRealtimeToolCapability(readSafeHeader(event, REALTIME_VOICE_CAPABILITY_HEADER), auth);
|
|
581
559
|
if (!capability) {
|
|
582
560
|
setResponseStatus(event, 403);
|
|
583
561
|
return { error: "Invalid or expired realtime voice capability" };
|
|
@@ -648,7 +626,12 @@ function createToolHandler(toolsByName, capabilities, options) {
|
|
|
648
626
|
return {
|
|
649
627
|
callId: request.callId,
|
|
650
628
|
...result,
|
|
651
|
-
...(expandedTools.length > 0
|
|
629
|
+
...(expandedTools.length > 0
|
|
630
|
+
? {
|
|
631
|
+
expandedTools,
|
|
632
|
+
capability: mintRealtimeToolCapability(auth, capability),
|
|
633
|
+
}
|
|
634
|
+
: {}),
|
|
652
635
|
};
|
|
653
636
|
}
|
|
654
637
|
catch (error) {
|
|
@@ -669,10 +652,9 @@ export function mountRealtimeVoiceRoutes(nitroApp, actions, options) {
|
|
|
669
652
|
}
|
|
670
653
|
const tools = buildRealtimeTools(actions);
|
|
671
654
|
const toolsByName = new Map(tools.map((tool) => [tool.name, tool]));
|
|
672
|
-
const capabilities = new Map();
|
|
673
655
|
const app = getH3App(nitroApp);
|
|
674
|
-
app.use(REALTIME_VOICE_SESSION_PATH, createSessionHandler(tools,
|
|
675
|
-
app.use(REALTIME_VOICE_TOOL_PATH, createToolHandler(toolsByName,
|
|
656
|
+
app.use(REALTIME_VOICE_SESSION_PATH, createSessionHandler(tools, options));
|
|
657
|
+
app.use(REALTIME_VOICE_TOOL_PATH, createToolHandler(toolsByName, options));
|
|
676
658
|
return {
|
|
677
659
|
sessionPath: REALTIME_VOICE_SESSION_PATH,
|
|
678
660
|
toolPath: REALTIME_VOICE_TOOL_PATH,
|
|
@@ -121,6 +121,49 @@ export declare function verifyRealtimeSubscribeToken(token: string, expected: {
|
|
|
121
121
|
projectId: string;
|
|
122
122
|
key: string;
|
|
123
123
|
}): RealtimeVerifyResult;
|
|
124
|
+
/** Payload `typ` discriminator for realtime voice tool capabilities. */
|
|
125
|
+
export declare const REALTIME_VOICE_CAPABILITY_TOKEN_TYPE = "rt-voice-capability";
|
|
126
|
+
/** Inputs for {@link signRealtimeVoiceCapability}. */
|
|
127
|
+
export interface RealtimeVoiceCapabilityClaims {
|
|
128
|
+
/** Authenticated caller. Re-checked against the session on every tool call. */
|
|
129
|
+
userEmail: string;
|
|
130
|
+
orgId?: string;
|
|
131
|
+
/** Binds the capability to the tab that opened the session. */
|
|
132
|
+
browserTabId?: string;
|
|
133
|
+
/** Tool names packed into the session's opening manifest. */
|
|
134
|
+
toolNames: readonly string[];
|
|
135
|
+
/** Names added later by a successful `tool-search`, capped separately. */
|
|
136
|
+
discoveredToolNames?: readonly string[];
|
|
137
|
+
ttlSeconds?: number;
|
|
138
|
+
}
|
|
139
|
+
/** Result of {@link verifyRealtimeVoiceCapability}. */
|
|
140
|
+
export type RealtimeVoiceCapabilityVerifyResult = {
|
|
141
|
+
ok: true;
|
|
142
|
+
userEmail: string;
|
|
143
|
+
orgId?: string;
|
|
144
|
+
browserTabId?: string;
|
|
145
|
+
toolNames: string[];
|
|
146
|
+
discoveredToolNames: string[];
|
|
147
|
+
} | {
|
|
148
|
+
ok: false;
|
|
149
|
+
reason: string;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Mint a capability authorising `claims.toolNames` for one realtime voice
|
|
153
|
+
* session. The token is a bounded manifest scope layered on top of the
|
|
154
|
+
* caller's session cookie — never a standalone credential.
|
|
155
|
+
*/
|
|
156
|
+
export declare function signRealtimeVoiceCapability(claims: RealtimeVoiceCapabilityClaims, ttlSecondsDefault: number): string;
|
|
157
|
+
/**
|
|
158
|
+
* Verify a realtime voice capability and confirm it was minted for exactly the
|
|
159
|
+
* identity now making the request. Identity mismatch is reported separately
|
|
160
|
+
* from a bad signature so callers can tell a replay from a stale tab.
|
|
161
|
+
*/
|
|
162
|
+
export declare function verifyRealtimeVoiceCapability(token: string | undefined, expected: {
|
|
163
|
+
userEmail: string;
|
|
164
|
+
orgId?: string;
|
|
165
|
+
browserTabId?: string;
|
|
166
|
+
}): RealtimeVoiceCapabilityVerifyResult;
|
|
124
167
|
/** Payload `typ` discriminator for gateway access-check tokens. */
|
|
125
168
|
export declare const GATEWAY_ACCESS_TOKEN_TYPE = "rt-access-check";
|
|
126
169
|
/** Inputs for {@link signGatewayAccessToken}. */
|
|
@@ -241,6 +241,80 @@ export function verifyRealtimeSubscribeToken(token, expected) {
|
|
|
241
241
|
absExp: claims.absExp,
|
|
242
242
|
};
|
|
243
243
|
}
|
|
244
|
+
// ── Realtime voice tool capabilities ─────────────────────────────────────────
|
|
245
|
+
// Signed, not stored: minting and spending are separate requests, and under a
|
|
246
|
+
// serverless preset they land on different instances.
|
|
247
|
+
/** Payload `typ` discriminator for realtime voice tool capabilities. */
|
|
248
|
+
export const REALTIME_VOICE_CAPABILITY_TOKEN_TYPE = "rt-voice-capability";
|
|
249
|
+
/**
|
|
250
|
+
* Mint a capability authorising `claims.toolNames` for one realtime voice
|
|
251
|
+
* session. The token is a bounded manifest scope layered on top of the
|
|
252
|
+
* caller's session cookie — never a standalone credential.
|
|
253
|
+
*/
|
|
254
|
+
export function signRealtimeVoiceCapability(claims, ttlSecondsDefault) {
|
|
255
|
+
const payload = {
|
|
256
|
+
typ: REALTIME_VOICE_CAPABILITY_TOKEN_TYPE,
|
|
257
|
+
userEmail: claims.userEmail.trim().toLowerCase(),
|
|
258
|
+
toolNames: [...claims.toolNames],
|
|
259
|
+
exp: Math.floor(Date.now() / 1000) + (claims.ttlSeconds ?? ttlSecondsDefault),
|
|
260
|
+
};
|
|
261
|
+
if (claims.orgId)
|
|
262
|
+
payload.orgId = claims.orgId;
|
|
263
|
+
if (claims.browserTabId)
|
|
264
|
+
payload.browserTabId = claims.browserTabId;
|
|
265
|
+
if (claims.discoveredToolNames?.length) {
|
|
266
|
+
payload.discovered = [...claims.discoveredToolNames];
|
|
267
|
+
}
|
|
268
|
+
const payloadStr = base64UrlEncode(JSON.stringify(payload));
|
|
269
|
+
return `${payloadStr}.${hmacB64(payloadStr, getSigningKey())}`;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Verify a realtime voice capability and confirm it was minted for exactly the
|
|
273
|
+
* identity now making the request. Identity mismatch is reported separately
|
|
274
|
+
* from a bad signature so callers can tell a replay from a stale tab.
|
|
275
|
+
*/
|
|
276
|
+
export function verifyRealtimeVoiceCapability(token, expected) {
|
|
277
|
+
if (typeof token !== "string" || !token.includes(".")) {
|
|
278
|
+
return { ok: false, reason: "malformed" };
|
|
279
|
+
}
|
|
280
|
+
const [payloadStr, sig] = token.split(".", 2);
|
|
281
|
+
if (!payloadStr || !sig)
|
|
282
|
+
return { ok: false, reason: "malformed" };
|
|
283
|
+
if (!timingSafeEqualB64(sig, hmacB64(payloadStr, getSigningKey()))) {
|
|
284
|
+
return { ok: false, reason: "bad_signature" };
|
|
285
|
+
}
|
|
286
|
+
let claims;
|
|
287
|
+
try {
|
|
288
|
+
claims = JSON.parse(base64UrlDecode(payloadStr).toString("utf8"));
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
return { ok: false, reason: "bad_payload" };
|
|
292
|
+
}
|
|
293
|
+
if (claims.typ !== REALTIME_VOICE_CAPABILITY_TOKEN_TYPE) {
|
|
294
|
+
return { ok: false, reason: "wrong_type" };
|
|
295
|
+
}
|
|
296
|
+
if (typeof claims.exp !== "number" || !Array.isArray(claims.toolNames)) {
|
|
297
|
+
return { ok: false, reason: "bad_payload" };
|
|
298
|
+
}
|
|
299
|
+
if (claims.exp * 1000 < Date.now())
|
|
300
|
+
return { ok: false, reason: "expired" };
|
|
301
|
+
if (claims.userEmail !== expected.userEmail.trim().toLowerCase() ||
|
|
302
|
+
claims.orgId !== expected.orgId ||
|
|
303
|
+
claims.browserTabId !== expected.browserTabId) {
|
|
304
|
+
return { ok: false, reason: "identity_mismatch" };
|
|
305
|
+
}
|
|
306
|
+
const stringsOnly = (value) => Array.isArray(value)
|
|
307
|
+
? value.filter((name) => typeof name === "string")
|
|
308
|
+
: [];
|
|
309
|
+
return {
|
|
310
|
+
ok: true,
|
|
311
|
+
userEmail: claims.userEmail,
|
|
312
|
+
orgId: claims.orgId,
|
|
313
|
+
browserTabId: claims.browserTabId,
|
|
314
|
+
toolNames: stringsOnly(claims.toolNames),
|
|
315
|
+
discoveredToolNames: stringsOnly(claims.discovered),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
244
318
|
// ── Gateway access-check tokens ──────────────────────────────────────────────
|
|
245
319
|
//
|
|
246
320
|
// The hosted gateway has no access to an app's shareable-resource registry, so
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.145.
|
|
3
|
+
"version": "0.145.5",
|
|
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": {
|
|
@@ -413,7 +413,7 @@
|
|
|
413
413
|
"yjs": "^13.6.31",
|
|
414
414
|
"zod": "^4.3.6",
|
|
415
415
|
"@agent-native/recap-cli": "0.5.3",
|
|
416
|
-
"@agent-native/toolkit": "^0.13.
|
|
416
|
+
"@agent-native/toolkit": "^0.13.4"
|
|
417
417
|
},
|
|
418
418
|
"devDependencies": {
|
|
419
419
|
"@ai-sdk/anthropic": "^3.0.71",
|