@agent-native/core 0.209.0 → 0.209.1-nightly-20261015005848
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/dist/agent/production-agent.d.ts +12 -1
- package/dist/agent/production-agent.js +25 -3
- package/dist/client/agent-sidebar-state.d.ts +1 -1
- package/dist/client/agent-sidebar-state.js +97 -5
- package/dist/coding-tools/run-code.js +5 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/mcp-client/oauth-routes.js +20 -13
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.js +3 -0
- package/dist/provider-api/index.js +29 -2
- package/dist/provider-api/staging.js +3 -0
- package/dist/server/agent-chat/action-filters-a2a.js +11 -0
- package/dist/server/agent-chat/framework-prompts.js +6 -2
- package/dist/server/agent-chat-plugin.js +6 -3
- package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
- package/dist/server/google-oauth.d.ts +8 -0
- package/dist/server/google-oauth.js +8 -5
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/request-context.d.ts +3 -0
- package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
- package/docs/content/create-with-an-agent.mdx +160 -0
- package/docs/content/getting-started.mdx +3 -0
- package/package.json +3 -3
|
@@ -357,7 +357,12 @@ export interface ProductionAgentOptions {
|
|
|
357
357
|
runNoProgressTimeoutMs?: number;
|
|
358
358
|
durableBackgroundRuns?: boolean;
|
|
359
359
|
onRunStart?: (send: (event: AgentChatEvent) => void, threadId: string, runId: string) => void | Promise<void>;
|
|
360
|
-
|
|
360
|
+
/**
|
|
361
|
+
* Called when the run's engine, model, and effort resolve, and again before
|
|
362
|
+
* each model call, because a call can run on a rate-limit fallback model or
|
|
363
|
+
* at the lower effort an empty-response retry steps down to.
|
|
364
|
+
*/
|
|
365
|
+
onEngineResolved?: (engine: AgentEngine, model: string, reasoningEffort: ReasoningEffort | undefined) => void;
|
|
361
366
|
resolveOwnerEmail?: (event: any) => string | Promise<string>;
|
|
362
367
|
finalResponseGuard?: AgentLoopFinalResponseGuard;
|
|
363
368
|
skipFilesContext?: boolean;
|
|
@@ -674,6 +679,12 @@ export declare function runAgentLoop(opts: {
|
|
|
674
679
|
unknownWriteToolNames?: Set<string>;
|
|
675
680
|
internalContinuation?: boolean;
|
|
676
681
|
onModelInput?: ModelInputObserver;
|
|
682
|
+
/**
|
|
683
|
+
* The model and effort each model call uses. Either can change mid-run: a
|
|
684
|
+
* rate limit falls back to another model, and an empty response steps effort
|
|
685
|
+
* down.
|
|
686
|
+
*/
|
|
687
|
+
onModelSettings?: (model: string, reasoningEffort: ReasoningEffort | undefined) => void;
|
|
677
688
|
onUsage?: (usage: AgentLoopUsage) => void;
|
|
678
689
|
onOutcome?: (outcome: AgentLoopOutcome) => void;
|
|
679
690
|
ownerEmail?: string | null;
|
|
@@ -4584,6 +4584,7 @@ export async function runAgentLoop(opts) {
|
|
|
4584
4584
|
: effectiveReasoningEffort,
|
|
4585
4585
|
providerOptions,
|
|
4586
4586
|
};
|
|
4587
|
+
opts.onModelSettings?.(model, effectiveReasoningEffort);
|
|
4587
4588
|
usage.llmCalls = (usage.llmCalls ?? 0) + 1;
|
|
4588
4589
|
notifyModelInputObserver(opts.onModelInput, engineMessages);
|
|
4589
4590
|
const eventStream = engine.stream(streamOpts);
|
|
@@ -8539,7 +8540,7 @@ export function createProductionAgentHandler(options) {
|
|
|
8539
8540
|
configuredEffort: options.reasoningEffort,
|
|
8540
8541
|
});
|
|
8541
8542
|
const modelSupportsVision = isAgentModelVisionCapable(effectiveModel, engine.capabilities.vision === true);
|
|
8542
|
-
options.onEngineResolved?.(engine, effectiveModel);
|
|
8543
|
+
options.onEngineResolved?.(engine, effectiveModel, reasoningEffort);
|
|
8543
8544
|
console.log(`[agent-chat] resolved engine=${engine.name} model=${effectiveModel} requestModel=${requestModel ?? "(none)"} requestEngine=${requestEngine ?? "(none)"} modelSource=${modelSelectionSource} engineDefault=${engine.defaultModel} effort=${reasoningEffort ?? "unset"} turnId=${requestTurnId ?? "(none)"}`);
|
|
8544
8545
|
if (!(await isResolvedEngineUsableForRequest(engine, {
|
|
8545
8546
|
apiKey: effectiveApiKey,
|
|
@@ -9792,9 +9793,26 @@ export function createProductionAgentHandler(options) {
|
|
|
9792
9793
|
: "") +
|
|
9793
9794
|
`\n</custom-agent-profile>`;
|
|
9794
9795
|
let responseText = "";
|
|
9795
|
-
|
|
9796
|
+
// A profile is its own model conversation running beside
|
|
9797
|
+
// others, so it gets its own run context: its model and effort
|
|
9798
|
+
// never stand in for another loop's, and it starts with
|
|
9799
|
+
// nothing read, since the run's reads are what the run's
|
|
9800
|
+
// model saw. New per-conversation fields reset here too.
|
|
9801
|
+
const profileRun = {
|
|
9802
|
+
...getRequestRunContext(),
|
|
9803
|
+
model: profile.model ?? model,
|
|
9804
|
+
reasoningEffort,
|
|
9805
|
+
toolSearchReads: undefined,
|
|
9806
|
+
extensionContentReads: undefined,
|
|
9807
|
+
extensionExcerptReads: undefined,
|
|
9808
|
+
};
|
|
9809
|
+
const profileLoopOpts = {
|
|
9796
9810
|
engine,
|
|
9797
9811
|
model: profile.model ?? model,
|
|
9812
|
+
onModelSettings: (loopModel, effort) => {
|
|
9813
|
+
profileRun.model = loopModel;
|
|
9814
|
+
profileRun.reasoningEffort = effort;
|
|
9815
|
+
},
|
|
9798
9816
|
systemPrompt: profilePrompt,
|
|
9799
9817
|
tools: requestTools,
|
|
9800
9818
|
availableTools: availableRequestTools,
|
|
@@ -9848,7 +9866,8 @@ export function createProductionAgentHandler(options) {
|
|
|
9848
9866
|
...(resolvedRunSoftTimeoutMs > 0
|
|
9849
9867
|
? { runSoftTimeoutMs: resolvedRunSoftTimeoutMs }
|
|
9850
9868
|
: {}),
|
|
9851
|
-
}
|
|
9869
|
+
};
|
|
9870
|
+
const subUsage = await runWithRequestContext({ ...getRequestContext(), run: profileRun }, () => runAgentLoop(profileLoopOpts));
|
|
9852
9871
|
await control.flushEvents();
|
|
9853
9872
|
try {
|
|
9854
9873
|
const ownerEmail = options.resolveOwnerEmail
|
|
@@ -9968,6 +9987,9 @@ export function createProductionAgentHandler(options) {
|
|
|
9968
9987
|
const agentLoopOpts = {
|
|
9969
9988
|
engine,
|
|
9970
9989
|
model: effectiveModel,
|
|
9990
|
+
// Custom agent profile loops report into their own run context
|
|
9991
|
+
// copies, so only this loop sets the run's model and effort.
|
|
9992
|
+
onModelSettings: (model, effort) => options.onEngineResolved?.(engine, model, effort),
|
|
9971
9993
|
runId,
|
|
9972
9994
|
followUpSuggestions: true,
|
|
9973
9995
|
systemPrompt: requestSystemPrompt,
|
|
@@ -17,7 +17,7 @@ export declare function getAgentSidebarMaxWidth(viewportWidth?: number): number;
|
|
|
17
17
|
export declare function clampAgentSidebarWidth(width: number, viewportWidth?: number): number;
|
|
18
18
|
export declare function getAgentSidebarOpenPreferenceKey(storageKey?: string | null): string;
|
|
19
19
|
export declare function dispatchAgentSidebarStateChange(detail: AgentSidebarStateChangeDetail): void;
|
|
20
|
-
export declare function setAgentSidebarOpenPreference(open: boolean, storageKey?: string | null):
|
|
20
|
+
export declare function setAgentSidebarOpenPreference(open: boolean, storageKey?: string | null): boolean;
|
|
21
21
|
export declare function requestAgentSidebarOpen(): void;
|
|
22
22
|
export declare function getAgentSidebarUrlOpenOverride(): boolean | null;
|
|
23
23
|
export declare function consumeAgentSidebarUrlOpenOverride(storageKey?: string | null): boolean | null;
|
|
@@ -34,6 +34,75 @@ export function getAgentSidebarOpenPreferenceKey(storageKey) {
|
|
|
34
34
|
const suffix = storageKey?.trim();
|
|
35
35
|
return suffix ? `agent-native.${suffix}.sidebar-open` : SIDEBAR_OPEN_KEY;
|
|
36
36
|
}
|
|
37
|
+
function getExplicitUrlOpenSessionKey(storageKey) {
|
|
38
|
+
return `${getAgentSidebarOpenPreferenceKey(storageKey)}.explicit-url-open`;
|
|
39
|
+
}
|
|
40
|
+
function getExplicitUrlOpenSessionIntent(storageKey) {
|
|
41
|
+
if (typeof window === "undefined")
|
|
42
|
+
return null;
|
|
43
|
+
try {
|
|
44
|
+
return (window.sessionStorage.getItem(getExplicitUrlOpenSessionKey(storageKey)) === "true");
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// coercion-ok: null distinguishes unreadable storage from a missing false intent.
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function setExplicitUrlOpenSessionIntent(open, storageKey) {
|
|
52
|
+
if (typeof window === "undefined")
|
|
53
|
+
return false;
|
|
54
|
+
try {
|
|
55
|
+
const key = getExplicitUrlOpenSessionKey(storageKey);
|
|
56
|
+
if (open) {
|
|
57
|
+
window.sessionStorage.setItem(key, "true");
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
window.sessionStorage.removeItem(key);
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// coercion-ok: false keeps the explicit URL intent available when the session write fails.
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function writeAgentSidebarOpenPreference(open, storageKey) {
|
|
70
|
+
if (typeof window === "undefined")
|
|
71
|
+
return false;
|
|
72
|
+
try {
|
|
73
|
+
window.localStorage.setItem(getAgentSidebarOpenPreferenceKey(storageKey), String(open));
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// coercion-ok: false keeps the URL override retryable when preference persistence fails.
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function replaceAgentSidebarUrlOpenOverride(open) {
|
|
82
|
+
if (typeof window === "undefined")
|
|
83
|
+
return false;
|
|
84
|
+
try {
|
|
85
|
+
const url = new URL(window.location.href);
|
|
86
|
+
if (open === null) {
|
|
87
|
+
url.searchParams.delete(AGENT_SIDEBAR_QUERY_PARAM);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
url.searchParams.set(AGENT_SIDEBAR_QUERY_PARAM, open
|
|
91
|
+
? AGENT_SIDEBAR_QUERY_VALUE_OPEN
|
|
92
|
+
: AGENT_SIDEBAR_QUERY_VALUE_CLOSED);
|
|
93
|
+
}
|
|
94
|
+
const nextUrl = `${url.pathname}${url.search}${url.hash}`;
|
|
95
|
+
const currentUrl = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
96
|
+
if (nextUrl === currentUrl)
|
|
97
|
+
return true;
|
|
98
|
+
window.history.replaceState(window.history.state, "", nextUrl);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// coercion-ok: false tells the caller the URL intent could not be updated.
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
37
106
|
export function dispatchAgentSidebarStateChange(detail) {
|
|
38
107
|
if (typeof window === "undefined")
|
|
39
108
|
return;
|
|
@@ -43,11 +112,21 @@ export function dispatchAgentSidebarStateChange(detail) {
|
|
|
43
112
|
}
|
|
44
113
|
export function setAgentSidebarOpenPreference(open, storageKey) {
|
|
45
114
|
if (typeof window === "undefined")
|
|
46
|
-
return;
|
|
47
|
-
|
|
48
|
-
|
|
115
|
+
return false;
|
|
116
|
+
const urlOverride = getAgentSidebarUrlOpenOverride();
|
|
117
|
+
const sessionIntentSaved = urlOverride !== null || !open
|
|
118
|
+
? setExplicitUrlOpenSessionIntent(open, storageKey)
|
|
119
|
+
: true;
|
|
120
|
+
const preferenceSaved = writeAgentSidebarOpenPreference(open, storageKey);
|
|
121
|
+
const storageSaved = sessionIntentSaved && preferenceSaved;
|
|
122
|
+
if (urlOverride !== null || !storageSaved) {
|
|
123
|
+
let urlOverrideSaved = replaceAgentSidebarUrlOpenOverride(storageSaved ? null : open);
|
|
124
|
+
if (storageSaved && !urlOverrideSaved) {
|
|
125
|
+
urlOverrideSaved = replaceAgentSidebarUrlOpenOverride(open);
|
|
126
|
+
}
|
|
127
|
+
return storageSaved && urlOverrideSaved;
|
|
49
128
|
}
|
|
50
|
-
|
|
129
|
+
return storageSaved;
|
|
51
130
|
}
|
|
52
131
|
export function requestAgentSidebarOpen() {
|
|
53
132
|
if (typeof window === "undefined")
|
|
@@ -72,7 +151,10 @@ export function consumeAgentSidebarUrlOpenOverride(storageKey) {
|
|
|
72
151
|
const override = getAgentSidebarUrlOpenOverride();
|
|
73
152
|
if (override === null || typeof window === "undefined")
|
|
74
153
|
return override;
|
|
75
|
-
|
|
154
|
+
const preferenceSaved = writeAgentSidebarOpenPreference(override, storageKey);
|
|
155
|
+
const sessionIntentSaved = setExplicitUrlOpenSessionIntent(override, storageKey);
|
|
156
|
+
if (!preferenceSaved || !sessionIntentSaved)
|
|
157
|
+
return override;
|
|
76
158
|
try {
|
|
77
159
|
const url = new URL(window.location.href);
|
|
78
160
|
url.searchParams.delete(AGENT_SIDEBAR_QUERY_PARAM);
|
|
@@ -125,6 +207,16 @@ export function getInitialAgentSidebarOpen(defaultOpen, storageKey, { ignoreUrlO
|
|
|
125
207
|
if (urlOverride !== null)
|
|
126
208
|
return urlOverride;
|
|
127
209
|
}
|
|
210
|
+
if (getExplicitUrlOpenSessionIntent(storageKey) === true) {
|
|
211
|
+
try {
|
|
212
|
+
if (window.localStorage.getItem(getAgentSidebarOpenPreferenceKey(storageKey)) !== "false") {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
128
220
|
if (hasChatThreadDeepLink())
|
|
129
221
|
return true;
|
|
130
222
|
if (typeof window !== "undefined" &&
|
|
@@ -105,8 +105,8 @@ export function createRunCodeEntry(getActions, opts = {}) {
|
|
|
105
105
|
" Supports stageAs/saveToFile/fetchAllPages; use cursorBodyPath for POST-body pagination.",
|
|
106
106
|
" Example: `const data = await providerFetch('<provider-id>', '/records', { query: { limit: 100 } });`",
|
|
107
107
|
" - `providerRequest(provider, path, init?)` — same authenticated call, but returns the full provider-api envelope with request, response status/headers, truncation, and body metadata.",
|
|
108
|
-
" - `providerFetchAll(provider, path, init?)` — generic pagination helper for cursor, page, and offset APIs. Pass `pagination: { itemsPath, cursorPath or nextCursorPath, cursorParam or cursorBodyPath, pageParam, offsetParam, pageSize, maxPages }`. Returns `{ items, pages, pageCount, itemCount, hasMore, lastCursor, stoppedReason }`.",
|
|
109
|
-
" - `providerSearchAll(provider, path, init?, options?)` — streaming search helper for broad provider corpora such as transcripts, messages, tickets, issues, notes, events, or documents. Use this before hand-written loops when
|
|
108
|
+
" - `providerFetchAll(provider, path, init?)` — generic pagination helper for cursor, page, and offset APIs, for a cohort already narrowed by a server-side filter (not for finding one entity or topic). Pass `pagination: { itemsPath, cursorPath or nextCursorPath, cursorParam or cursorBodyPath, pageParam, offsetParam, pageSize, maxPages }`. Returns `{ items, pages, pageCount, itemCount, hasMore, lastCursor, stoppedReason }`.",
|
|
109
|
+
" - `providerSearchAll(provider, path, init?, options?)` — streaming search helper for broad provider corpora such as transcripts, messages, tickets, issues, notes, events, or documents. Use this before hand-written loops when a bounded cohort must be searched for terms/phrases/regexes or an absence claim needs proof; if the provider has a search or semantic tool for the question, use that first. Pass the same `pagination` config as `providerFetchAll`, plus options like `{ query, queries, terms, regex, textPaths, idPaths, metadataPaths, maxHits }`. Returns structured hits with item ids, paths, snippets, page/item indexes, and coverage fields (`pageCount`, `itemCount`, `hasMore`, `stoppedReason`).",
|
|
110
110
|
" - `webFetch(url, init?)` — outbound HTTP request via the web-request action.",
|
|
111
111
|
" Returns `{ status, body }` where body is the response text. Supports responseMode, extract, includeLinks, search, maxChars, and saveToFile.",
|
|
112
112
|
" Example: `const { body } = await webFetch('https://api.example.com/data', { responseMode: 'raw' });`",
|
|
@@ -1057,6 +1057,9 @@ async function providerFetch(provider, apiPath, init = {}) {
|
|
|
1057
1057
|
const detail = typeof r.text === "string" ? r.text : JSON.stringify(r.json ?? "");
|
|
1058
1058
|
throw new Error(\`Provider request failed (\${r.status}): \${String(detail).slice(0, 500)}\`);
|
|
1059
1059
|
}
|
|
1060
|
+
if (r.truncated === true) {
|
|
1061
|
+
throw new Error("Provider response was cut at the response size limit, so its body is incomplete. Request smaller pages or add a server-side filter; use providerRequest() to inspect the partial body.");
|
|
1062
|
+
}
|
|
1060
1063
|
return r.json !== undefined ? r.json : r.text;
|
|
1061
1064
|
}
|
|
1062
1065
|
return parsed;
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -13,8 +13,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
13
13
|
};
|
|
14
14
|
}>>;
|
|
15
15
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
ok?: undefined;
|
|
17
16
|
error: string;
|
|
17
|
+
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
@@ -201,9 +201,28 @@ async function handleMcpOAuthStart(event) {
|
|
|
201
201
|
if (useRootGoogleCallback && !workspaceAppId) {
|
|
202
202
|
return refuse(event, 400, "Workspace MCP OAuth is missing its app callback id.");
|
|
203
203
|
}
|
|
204
|
+
const requestContext = {
|
|
205
|
+
userEmail: session.email,
|
|
206
|
+
orgId: org?.orgId ?? undefined,
|
|
207
|
+
};
|
|
208
|
+
let clientInformation;
|
|
209
|
+
try {
|
|
210
|
+
clientInformation = await runWithRequestContext(requestContext, () => resolveManagedMcpOAuthClient(urlCheck.url));
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
return mcpOAuthStartFailureResponse(event, resolveMcpOAuthStartError(error));
|
|
214
|
+
}
|
|
215
|
+
if (managedMcpOAuthClientFor(urlCheck.url)?.requireClientCredentials &&
|
|
216
|
+
!clientInformation) {
|
|
217
|
+
return refuse(event, 400, MCP_OAUTH_MANAGED_CLIENT_MISSING_MESSAGE);
|
|
218
|
+
}
|
|
219
|
+
// A pre-registered client has a fixed redirect URI on the shared workspace
|
|
220
|
+
// callback. A dynamically registered client registers whatever URI this
|
|
221
|
+
// flow sends, so keep it on this app's mount: the provider then returns the
|
|
222
|
+
// user straight to the app that owns the flow cookie.
|
|
204
223
|
const redirectUri = resolveOAuthRedirectUri(event, useRootGoogleCallback
|
|
205
224
|
? "/_agent-native/google/callback"
|
|
206
|
-
: "/_agent-native/mcp/servers/oauth/callback");
|
|
225
|
+
: "/_agent-native/mcp/servers/oauth/callback", { appScopedCallback: !useRootGoogleCallback && !clientInformation });
|
|
207
226
|
if (!redirectUri) {
|
|
208
227
|
return refuse(event, 400, "Invalid MCP OAuth redirect URI.");
|
|
209
228
|
}
|
|
@@ -218,17 +237,8 @@ async function handleMcpOAuthStart(event) {
|
|
|
218
237
|
})
|
|
219
238
|
: crypto.randomUUID();
|
|
220
239
|
const safeReturnUrl = returnUrl ? safeReturnPath(returnUrl) : undefined;
|
|
221
|
-
const requestContext = {
|
|
222
|
-
userEmail: session.email,
|
|
223
|
-
orgId: org?.orgId ?? undefined,
|
|
224
|
-
};
|
|
225
240
|
try {
|
|
226
241
|
const started = await runWithRequestContext(requestContext, async () => {
|
|
227
|
-
const clientInformation = await resolveManagedMcpOAuthClient(urlCheck.url);
|
|
228
|
-
if (managedMcpOAuthClientFor(urlCheck.url)?.requireClientCredentials &&
|
|
229
|
-
!clientInformation) {
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
242
|
const storedCredentials = reconnectServer?.oauthSecretKey && reconnectScopeId
|
|
233
243
|
? await readMcpOAuthCredentials({
|
|
234
244
|
key: reconnectServer.oauthSecretKey,
|
|
@@ -259,9 +269,6 @@ async function handleMcpOAuthStart(event) {
|
|
|
259
269
|
: {}),
|
|
260
270
|
});
|
|
261
271
|
});
|
|
262
|
-
if (!started) {
|
|
263
|
-
return refuse(event, 400, MCP_OAUTH_MANAGED_CLIENT_MISSING_MESSAGE);
|
|
264
|
-
}
|
|
265
272
|
const flow = {
|
|
266
273
|
name,
|
|
267
274
|
url: urlCheck.url.toString(),
|
|
@@ -663,6 +663,9 @@ async function callProvider(runtime, request) {
|
|
|
663
663
|
const detail = response.text ?? response.json ?? response.statusText ?? "";
|
|
664
664
|
throw new Error(`Provider request failed (${status}): ${stringifyBrief(detail, 500)}`);
|
|
665
665
|
}
|
|
666
|
+
if (response.truncated === true) {
|
|
667
|
+
throw new Error(`Provider response was cut at the response size limit, so this page is incomplete and would be recorded as empty. Lower the page size or fields, or add a filter in the job request.`);
|
|
668
|
+
}
|
|
666
669
|
return {
|
|
667
670
|
body: response.json ??
|
|
668
671
|
(typeof response.text === "string" ? tryParseJson(response.text) : null),
|
|
@@ -595,6 +595,7 @@ const PROVIDER_CONFIGS = {
|
|
|
595
595
|
},
|
|
596
596
|
],
|
|
597
597
|
notes: [
|
|
598
|
+
"To answer a question about a Gong account, deal, customer, or topic, use Gong's connected MCP tools (ask_account, ask_deal, generate_brief) when available; Gong searches the calls server-side and answers in one request. Gong's REST API has no account, company, or topic filter, so finding one account's calls or a topic through /calls or /calls/extensive means scanning the whole org, and those responses truncate at the size limit. If the Analytics app is connected, ask it with call-agent; it owns Gong search. Use the REST endpoints below only for exact counts, verbatim quotes, or proof of absence across a bounded cohort.",
|
|
598
599
|
"For broad corpus work, call /calls/extensive with provider-api-request and stageAs/saveToFile. Gong returns the next cursor at records.cursor and expects the next cursor in the POST body at cursor, so use pagination { nextCursorPath: 'records.cursor', cursorBodyPath: 'cursor' } for stageAs or fetchAllPages { cursorPath: 'records.cursor', cursorBodyPath: 'cursor' } for saveToFile.",
|
|
599
600
|
"The public Gong REST API does not expose an arbitrary transcript-text filter. Use configured keyword tracker results from /calls/extensive when they cover the requested term; otherwise batch transcripts with POST /calls/transcript and body { filter: { callIds: [...] } } after narrowing or staging call ids.",
|
|
600
601
|
],
|
|
@@ -1452,6 +1453,7 @@ export async function executeProviderApiRequest(args, runtime) {
|
|
|
1452
1453
|
contentType: resp.contentType,
|
|
1453
1454
|
status: resp.status,
|
|
1454
1455
|
ok: resp.ok,
|
|
1456
|
+
truncated: resp.truncated,
|
|
1455
1457
|
};
|
|
1456
1458
|
});
|
|
1457
1459
|
const allItemsJson = JSON.stringify(items, null, 2);
|
|
@@ -1492,6 +1494,9 @@ export async function executeProviderApiRequest(args, runtime) {
|
|
|
1492
1494
|
},
|
|
1493
1495
|
}), config);
|
|
1494
1496
|
if (args.saveToFile) {
|
|
1497
|
+
if (response.truncated) {
|
|
1498
|
+
throw new Error(`The response was cut at ${effectiveMaxBytes} bytes (${response.size} available), so saving it would persist an incomplete file. Narrow the request with a server-side filter or fetch it in pages with fetchAllPages.`);
|
|
1499
|
+
}
|
|
1495
1500
|
const rawText = response.text ??
|
|
1496
1501
|
(response.json !== undefined ? JSON.stringify(response.json) : "");
|
|
1497
1502
|
const saved = (await handleSaveToFile(args.saveToFile, rawText, response.contentType, response.status, response.ok));
|
|
@@ -1527,13 +1532,21 @@ export async function executeProviderApiRequest(args, runtime) {
|
|
|
1527
1532
|
},
|
|
1528
1533
|
response,
|
|
1529
1534
|
guidance: [
|
|
1530
|
-
"This was a raw provider API request. Use provider docs/spec URLs to choose endpoints and include method/path/status plus relevant filters in the methodology.
|
|
1535
|
+
"This was a raw provider API request. Use provider docs/spec URLs to choose endpoints and include method/path/status plus relevant filters in the methodology. A data API returns records: when the provider can filter, search, or answer the question itself, make it do that instead of pulling records to filter locally.",
|
|
1536
|
+
response.truncated
|
|
1537
|
+
? truncatedResponseGuidance(response.size, effectiveMaxBytes)
|
|
1538
|
+
: null,
|
|
1531
1539
|
providerAccessErrorGuidance(config, response.status),
|
|
1532
1540
|
]
|
|
1533
1541
|
.filter(Boolean)
|
|
1534
1542
|
.join(" "),
|
|
1535
1543
|
};
|
|
1536
1544
|
}
|
|
1545
|
+
// A truncated body is incomplete, not a smaller answer. Raising maxBytes and
|
|
1546
|
+
// paging the whole collection is the wrong recovery, so name the right ones.
|
|
1547
|
+
function truncatedResponseGuidance(size, maxBytes) {
|
|
1548
|
+
return `This response is incomplete: ${size} bytes came back and only the first ${maxBytes} are included. Do not raise maxBytes or page the whole collection to filter it locally. Add a server-side filter or query parameter, use the provider's own search or semantic tool, or run the scan with stageAs or provider-corpus-job. If none of those can express the question, say so or hand it to the app that owns this source.`;
|
|
1549
|
+
}
|
|
1537
1550
|
const PROVIDER_ACCESS_ERROR_STATUSES = new Set([401, 403, 404]);
|
|
1538
1551
|
function providerAccessErrorGuidance(config, status) {
|
|
1539
1552
|
if (!PROVIDER_ACCESS_ERROR_STATUSES.has(status))
|
|
@@ -2216,6 +2229,7 @@ async function executeCustomProviderApiRequest(args, customConfig, runtime) {
|
|
|
2216
2229
|
contentType: resp.contentType,
|
|
2217
2230
|
status: resp.status,
|
|
2218
2231
|
ok: resp.ok,
|
|
2232
|
+
truncated: resp.truncated,
|
|
2219
2233
|
};
|
|
2220
2234
|
});
|
|
2221
2235
|
const allItemsJson = JSON.stringify(items, null, 2);
|
|
@@ -2258,6 +2272,9 @@ async function executeCustomProviderApiRequest(args, customConfig, runtime) {
|
|
|
2258
2272
|
},
|
|
2259
2273
|
});
|
|
2260
2274
|
if (args.saveToFile) {
|
|
2275
|
+
if (response.truncated) {
|
|
2276
|
+
throw new Error(`The response was cut at ${effectiveMaxBytes} bytes (${response.size} available), so saving it would persist an incomplete file. Narrow the request with a server-side filter or fetch it in pages with fetchAllPages.`);
|
|
2277
|
+
}
|
|
2261
2278
|
const rawText = response.text ??
|
|
2262
2279
|
(response.json !== undefined ? JSON.stringify(response.json) : "");
|
|
2263
2280
|
const saved = (await handleSaveToFile(args.saveToFile, rawText, response.contentType, response.status, response.ok));
|
|
@@ -2295,7 +2312,14 @@ async function executeCustomProviderApiRequest(args, customConfig, runtime) {
|
|
|
2295
2312
|
headerNames: Object.keys(headers).filter((name) => name.toLowerCase() !== "authorization"),
|
|
2296
2313
|
},
|
|
2297
2314
|
response,
|
|
2298
|
-
guidance:
|
|
2315
|
+
guidance: [
|
|
2316
|
+
"This was a raw provider API request to a custom provider. Use provider docs URLs to choose endpoints.",
|
|
2317
|
+
response.truncated
|
|
2318
|
+
? truncatedResponseGuidance(response.size, effectiveMaxBytes)
|
|
2319
|
+
: null,
|
|
2320
|
+
]
|
|
2321
|
+
.filter(Boolean)
|
|
2322
|
+
.join(" "),
|
|
2299
2323
|
};
|
|
2300
2324
|
}
|
|
2301
2325
|
async function resolveCustomAuth(customConfig, runtime, ctx, args) {
|
|
@@ -4642,6 +4666,9 @@ async function fetchAllPages(config, executeOnePage) {
|
|
|
4642
4666
|
const preview = page.text.replace(/\s+/g, " ").trim().slice(0, 500);
|
|
4643
4667
|
throw new Error(`fetchAllPages request failed with HTTP ${page.status}${preview ? `: ${preview}` : ""}`);
|
|
4644
4668
|
}
|
|
4669
|
+
if (page.truncated) {
|
|
4670
|
+
throw new Error(`fetchAllPages page ${pageCount} was cut at the response size limit, so its items and next cursor are incomplete. Request smaller pages (a lower limit or fewer fields) or add a server-side filter.`);
|
|
4671
|
+
}
|
|
4645
4672
|
let body;
|
|
4646
4673
|
try {
|
|
4647
4674
|
body = JSON.parse(page.text);
|
|
@@ -202,6 +202,9 @@ export async function stagingExecuteRequest(args, execute, ctx) {
|
|
|
202
202
|
if (!response?.ok) {
|
|
203
203
|
fail(`Provider API returned status ${response?.status}: ${JSON.stringify(response?.json ?? response?.text).slice(0, 200)}`, { errorCode: "provider_api_rejected" });
|
|
204
204
|
}
|
|
205
|
+
if (response.truncated === true) {
|
|
206
|
+
fail(`Provider response page ${pageIndex + 1} was cut at the response size limit, so staging it would drop rows. Request smaller pages (a lower limit or fewer fields) or add a server-side filter.`, { errorCode: "provider_response_truncated" });
|
|
207
|
+
}
|
|
205
208
|
const body = response.json ??
|
|
206
209
|
(response.text ? tryParseJson(response.text) : null);
|
|
207
210
|
const pageRows = extractItemsArray(body, itemsPath);
|
|
@@ -10,6 +10,7 @@ import { getAppConfig } from "../../app-config/index.js";
|
|
|
10
10
|
import { isFrameworkGroupedAction } from "../../framework-tools.js";
|
|
11
11
|
import { isAuthenticatedReadAction, isAutoReadExcludedActionName, } from "../../mcp/build-server.js";
|
|
12
12
|
import { withConfiguredAppBasePath } from "../app-base-path.js";
|
|
13
|
+
import { ensureRequestRunContext } from "../request-context.js";
|
|
13
14
|
export function filterReadOnlyActions(actions) {
|
|
14
15
|
return Object.fromEntries(Object.entries(actions).filter(([, entry]) => entry.readOnly === true));
|
|
15
16
|
}
|
|
@@ -323,6 +324,16 @@ async function runDelegatedAgentLoop(runOptions, pluginOptions, timeoutOptions,
|
|
|
323
324
|
: delegatedHardToolResultCap,
|
|
324
325
|
},
|
|
325
326
|
finalResponseGuard: pluginOptions.finalResponseGuard,
|
|
327
|
+
// Actions this run calls record the model and effort from the run
|
|
328
|
+
// context, which a rate-limit fallback or an effort step-down changes.
|
|
329
|
+
onModelSettings: (model, reasoningEffort) => {
|
|
330
|
+
runOptions.onModelSettings?.(model, reasoningEffort);
|
|
331
|
+
const runContext = ensureRequestRunContext();
|
|
332
|
+
if (runContext) {
|
|
333
|
+
runContext.model = model;
|
|
334
|
+
runContext.reasoningEffort = reasoningEffort;
|
|
335
|
+
}
|
|
336
|
+
},
|
|
326
337
|
};
|
|
327
338
|
const execute = (loopOptions = resolvedRunOptions) => runner(loopOptions, pluginOptions.runSoftTimeoutMs, timeoutOptions);
|
|
328
339
|
if (!options.telemetry)
|
|
@@ -270,9 +270,13 @@ Available corpus-capable tools: ${available.join(", ")}.
|
|
|
270
270
|
|
|
271
271
|
This workflow does not apply to ordinary structured lookups, bounded aggregates, or counts grouped over one known source. For those requests, use the single most directly authoritative source, run one bounded query, and answer as soon as it succeeds. Do not cross-check or expand into a corpus workflow unless the user asks for multiple sources, exhaustive unstructured-record coverage, or an absence claim.
|
|
272
272
|
|
|
273
|
-
|
|
273
|
+
Retrieval ladder. Take the first rung that answers the question:
|
|
274
|
+
1. A tool shaped like the question: the provider's native search, semantic, or MCP tool, or an owning app via \`call-agent\`. It searches provider-side in one request but reports no coverage, so absence claims and exact counts start at rung 3 or carry a coverage caveat.
|
|
275
|
+
2. A server-side filter or query parameter on a bounded window.
|
|
276
|
+
3. A staged or durable scan (\`stageAs\`, \`provider-corpus-job\`) over an explicit cohort, only for exhaustive coverage, exact counts, absence claims, classification, or cross-source joins: fetch every relevant page or the bounded cohort, stage large responses, and reduce them with corpus jobs, staged-dataset queries, or code over the staged data.
|
|
277
|
+
Pulling a collection into context or a run-code loop to filter it locally is not a rung; it means rung 1 or 2 is missing, so say so.
|
|
274
278
|
|
|
275
|
-
When \`provider-corpus-job\` is available, prefer it for transcript/message/ticket/issue/document scans that may exceed one turn, need provider-side backoff, or need a defensible "not found" conclusion. Use operation="start" with mode="paginated-search" for any paginated provider endpoint, or mode="batch-search" when a prior cohort of ids/records must feed a second provider endpoint. Continue paused jobs with operation="continue" until status is completed or quota_wait, then read operation="results". In run-code,
|
|
279
|
+
When \`provider-corpus-job\` is available, prefer it for transcript/message/ticket/issue/document scans that may exceed one turn, need provider-side backoff, or need a defensible "not found" conclusion. Use operation="start" with mode="paginated-search" for any paginated provider endpoint, or mode="batch-search" when a prior cohort of ids/records must feed a second provider endpoint. Continue paused jobs with operation="continue" until status is completed or quota_wait, then read operation="results". In run-code, use providerFetchAll() only for short cursor/page/offset pagination over a cohort you already narrowed, and providerRequest() when response status, headers, or truncation metadata matters. Report source, filters, row counts, pagination/truncation, failed pages, quota_wait times, and remaining gaps.`;
|
|
276
280
|
}
|
|
277
281
|
export async function collectFiles(dir, prefix, depth, results) {
|
|
278
282
|
if (depth > 4 || results.length >= 500)
|
|
@@ -3697,11 +3697,12 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
3697
3697
|
jevContextCompact: leanPrompt || lazyContext,
|
|
3698
3698
|
initialToolNames: effectiveInitialToolNames,
|
|
3699
3699
|
...(options?.toolLimits ? { toolLimits: options.toolLimits } : {}),
|
|
3700
|
-
onEngineResolved: (engine, model) => {
|
|
3700
|
+
onEngineResolved: (engine, model, reasoningEffort) => {
|
|
3701
3701
|
const runCtx = ensureRequestRunContext();
|
|
3702
3702
|
if (runCtx) {
|
|
3703
3703
|
runCtx.engine = engine;
|
|
3704
3704
|
runCtx.model = model;
|
|
3705
|
+
runCtx.reasoningEffort = reasoningEffort;
|
|
3705
3706
|
}
|
|
3706
3707
|
},
|
|
3707
3708
|
onRunPrepared: persistSubmittedUserMessage,
|
|
@@ -3752,11 +3753,12 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
3752
3753
|
resolveActionSurface: (details) => resolveObservabilityReviewSummaryActionSurface(details, options?.resolveActionSurface),
|
|
3753
3754
|
skipFilesContext: true,
|
|
3754
3755
|
initialToolNames: effectiveInitialToolNames,
|
|
3755
|
-
onEngineResolved: (engine, model) => {
|
|
3756
|
+
onEngineResolved: (engine, model, reasoningEffort) => {
|
|
3756
3757
|
const runCtx = ensureRequestRunContext();
|
|
3757
3758
|
if (runCtx) {
|
|
3758
3759
|
runCtx.engine = engine;
|
|
3759
3760
|
runCtx.model = model;
|
|
3761
|
+
runCtx.reasoningEffort = reasoningEffort;
|
|
3760
3762
|
}
|
|
3761
3763
|
},
|
|
3762
3764
|
onRunPrepared: persistSubmittedUserMessage,
|
|
@@ -3938,11 +3940,12 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
3938
3940
|
skipFilesContext,
|
|
3939
3941
|
initialToolNames: effectiveInitialToolNames,
|
|
3940
3942
|
...(options?.toolLimits ? { toolLimits: options.toolLimits } : {}),
|
|
3941
|
-
onEngineResolved: (engine, model) => {
|
|
3943
|
+
onEngineResolved: (engine, model, reasoningEffort) => {
|
|
3942
3944
|
const runCtx = ensureRequestRunContext();
|
|
3943
3945
|
if (runCtx) {
|
|
3944
3946
|
runCtx.engine = engine;
|
|
3945
3947
|
runCtx.model = model;
|
|
3948
|
+
runCtx.reasoningEffort = reasoningEffort;
|
|
3946
3949
|
}
|
|
3947
3950
|
},
|
|
3948
3951
|
onRunPrepared: persistSubmittedUserMessage,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export declare function createAgentEngineOllamaModelsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
2
2
|
error: string;
|
|
3
|
-
ok?: undefined;
|
|
4
3
|
models?: undefined;
|
|
4
|
+
ok?: undefined;
|
|
5
5
|
} | {
|
|
6
|
-
error?: undefined;
|
|
7
6
|
ok: boolean;
|
|
8
7
|
models: string[];
|
|
8
|
+
error?: undefined;
|
|
9
9
|
}>>;
|
|
@@ -21,6 +21,14 @@ export declare function decodeNetlifyPreviewGoogleOAuthRelayState(value: string
|
|
|
21
21
|
export declare function wrapNetlifyPreviewGoogleOAuthState(event: H3Event, state: string, callbackPath?: string): string;
|
|
22
22
|
export type OAuthRedirectUriOptions = {
|
|
23
23
|
allowRootCallback?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Keep the callback under the app's own mount instead of the shared
|
|
26
|
+
* workspace root callback. Use for providers that register the redirect
|
|
27
|
+
* URI per flow (e.g. MCP dynamic client registration): the callback then
|
|
28
|
+
* lands on the app that started the flow without a root relay, which
|
|
29
|
+
* cannot recover the app id from an opaque state.
|
|
30
|
+
*/
|
|
31
|
+
appScopedCallback?: boolean;
|
|
24
32
|
useNetlifyPreviewGoogleOAuthRelay?: boolean;
|
|
25
33
|
};
|
|
26
34
|
/**
|
|
@@ -279,10 +279,15 @@ function isRequestUnderAppBasePath(event) {
|
|
|
279
279
|
];
|
|
280
280
|
return frameworkPrefixes.some((prefix) => requestPath === prefix || requestPath.startsWith(`${prefix}/`));
|
|
281
281
|
}
|
|
282
|
+
function usesRootOAuthCallback(pathname, options) {
|
|
283
|
+
return (!options.appScopedCallback &&
|
|
284
|
+
(isWorkspaceOAuthCallbackRelayEnabled() ||
|
|
285
|
+
options.allowRootCallback === true) &&
|
|
286
|
+
isFrameworkOAuthCallbackPath(pathname));
|
|
287
|
+
}
|
|
282
288
|
function getDefaultOAuthRedirectUrl(event, path, options = {}) {
|
|
283
289
|
const cleanPath = path.startsWith("/") ? path : `/${path}`;
|
|
284
|
-
if ((
|
|
285
|
-
isFrameworkOAuthCallbackPath(cleanPath)) {
|
|
290
|
+
if (usesRootOAuthCallback(cleanPath, options)) {
|
|
286
291
|
return `${getOrigin(event)}${publicFrameworkPath(cleanPath)}`;
|
|
287
292
|
}
|
|
288
293
|
const basePath = isRequestUnderAppBasePath(event) ? getAppBasePath() : "";
|
|
@@ -347,9 +352,7 @@ export function isAllowedOAuthRedirectUri(candidate, event, expectedOrigin = get
|
|
|
347
352
|
const allowedPrefixes = basePath && isRequestUnderAppBasePath(event)
|
|
348
353
|
? [
|
|
349
354
|
`${basePath}/_agent-native/`,
|
|
350
|
-
...((
|
|
351
|
-
options.allowRootCallback) &&
|
|
352
|
-
isFrameworkOAuthCallbackPath(pathname)
|
|
355
|
+
...(usesRootOAuthCallback(pathname, options)
|
|
353
356
|
? ["/_agent-native/"]
|
|
354
357
|
: []),
|
|
355
358
|
]
|
|
@@ -54,8 +54,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
|
|
|
54
54
|
expiresAt?: undefined;
|
|
55
55
|
ttlSeconds?: undefined;
|
|
56
56
|
} | {
|
|
57
|
+
error?: undefined;
|
|
57
58
|
token: string;
|
|
58
59
|
expiresAt: string;
|
|
59
60
|
ttlSeconds: number;
|
|
60
|
-
error?: undefined;
|
|
61
61
|
}>>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentActionScope } from "../agent/types.js";
|
|
2
2
|
import type { TrackingEventScope } from "../observability/tracing.js";
|
|
3
3
|
import type { ContextStatus } from "../shared/context-status.js";
|
|
4
|
+
import type { ReasoningEffort } from "../shared/reasoning-effort.js";
|
|
4
5
|
import type { SignupAttributionContext } from "./attribution.js";
|
|
5
6
|
export interface RequestRunContext {
|
|
6
7
|
waitUntil?: (promise: Promise<unknown>) => void;
|
|
@@ -19,6 +20,8 @@ export interface RequestRunContext {
|
|
|
19
20
|
systemPrompt?: string;
|
|
20
21
|
engine?: import("../agent/engine/types.js").AgentEngine;
|
|
21
22
|
model?: string;
|
|
23
|
+
/** The run's resolved thinking effort; unset when the model takes none. */
|
|
24
|
+
reasoningEffort?: ReasoningEffort;
|
|
22
25
|
allowedActionNames?: readonly string[];
|
|
23
26
|
actionScope?: Readonly<AgentActionScope>;
|
|
24
27
|
appAuthorization?: {
|
|
@@ -2,6 +2,7 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
2
2
|
resourceType: string;
|
|
3
3
|
resourceId: string;
|
|
4
4
|
}, {
|
|
5
|
+
role?: undefined;
|
|
5
6
|
ownerEmail: any;
|
|
6
7
|
visibility: any;
|
|
7
8
|
shares: any[];
|
|
@@ -11,7 +12,6 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
11
12
|
supportsGroupShares?: boolean;
|
|
12
13
|
};
|
|
13
14
|
orgId?: undefined;
|
|
14
|
-
role?: undefined;
|
|
15
15
|
agentReadable?: undefined;
|
|
16
16
|
} | {
|
|
17
17
|
ownerEmail: string;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Create an App with an AI Agent"
|
|
3
|
+
description: "Hand one prompt to Claude, ChatGPT, Codex, Cursor, or another AI agent and have it create and start a new Agent-Native app for you."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Create an App with an AI Agent
|
|
7
|
+
|
|
8
|
+
You can ask an AI agent to create your Agent-Native app. You paste one prompt, the agent runs the setup commands, and it shows you a running app when it is done.
|
|
9
|
+
|
|
10
|
+
If you would rather type the commands yourself, follow [Getting Started](/docs/getting-started) instead.
|
|
11
|
+
|
|
12
|
+
## Before you start
|
|
13
|
+
|
|
14
|
+
- An AI agent that can run terminal commands, such as Claude Code, Codex, or Cursor. Agents that only chat, such as ChatGPT or Claude on the web, can still help. See [Agents that can't run commands](#chat-only-agents).
|
|
15
|
+
- [Node.js](https://nodejs.org) 22.22 or later.
|
|
16
|
+
- An empty folder where the new app can live.
|
|
17
|
+
|
|
18
|
+
You don't need an API key to create the app. You only need one when you want the app's own agent to answer you. See [Connect an LLM](/docs/getting-started#connect-an-llm).
|
|
19
|
+
|
|
20
|
+
## Paste this prompt
|
|
21
|
+
|
|
22
|
+
Open your agent in the empty folder and paste the prompt below. Change `my-app` in the first line to the name you want. You only need to change it once.
|
|
23
|
+
|
|
24
|
+
```markdown
|
|
25
|
+
Create a new Agent-Native app in a new folder named my-app, and start
|
|
26
|
+
it. Use that name wherever a step below says APP_NAME.
|
|
27
|
+
|
|
28
|
+
1. Check that Node.js is version 22.22 or later and that pnpm is
|
|
29
|
+
available. If pnpm is missing, run `corepack enable`. If corepack
|
|
30
|
+
is not available either, run `npm install -g pnpm`. If Node.js is
|
|
31
|
+
too old, stop and tell me instead of installing it.
|
|
32
|
+
2. Run this command:
|
|
33
|
+
npx --yes @agent-native/core@latest create APP_NAME \
|
|
34
|
+
--standalone --template chat
|
|
35
|
+
3. In the new APP_NAME folder, run `pnpm install`.
|
|
36
|
+
4. Read APP_NAME/AGENTS.md so you know how the app is laid out.
|
|
37
|
+
5. Start the app with `pnpm dev` and keep it running in the background.
|
|
38
|
+
6. Read the local URL that `pnpm dev` prints. Check that it responds,
|
|
39
|
+
for example with `curl -I <url>`. Do not open a browser and do not
|
|
40
|
+
use headless browser tools. I will open the app myself.
|
|
41
|
+
7. Show me the result. Give me the local URL and the response you got
|
|
42
|
+
from it. Do not just say that setup is complete.
|
|
43
|
+
|
|
44
|
+
Rules:
|
|
45
|
+
|
|
46
|
+
- Do not invent or ask me to paste API keys. Connecting an LLM is
|
|
47
|
+
optional and I will do it myself.
|
|
48
|
+
- Do not print secrets from any .env file.
|
|
49
|
+
- If a command fails, show me the error and what you tried. Do not hide
|
|
50
|
+
it.
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## What the agent should do
|
|
54
|
+
|
|
55
|
+
A good run ends with these results, and you can check each one yourself:
|
|
56
|
+
|
|
57
|
+
- A new app folder, named `my-app` unless you changed it, that contains `actions/`, `app/`, and an `AGENTS.md` file.
|
|
58
|
+
- `pnpm dev` running without errors.
|
|
59
|
+
- A local URL, usually `http://localhost:8080`, that the agent has checked responds.
|
|
60
|
+
|
|
61
|
+
Then open the local URL in your own browser. On the Welcome screen, choose **Continue as local dev**. The Get started page appears.
|
|
62
|
+
|
|
63
|
+
If anything is missing, ask the agent to show the last command it ran and its output.
|
|
64
|
+
|
|
65
|
+
<Callout tone="info">
|
|
66
|
+
|
|
67
|
+
If the commands fail with peer dependency errors, an old copy of a package in the npx cache may be the cause. Ask the agent to run `npx clear-npx-cache` and try again.
|
|
68
|
+
|
|
69
|
+
</Callout>
|
|
70
|
+
|
|
71
|
+
## Pick the right setup for your agent
|
|
72
|
+
|
|
73
|
+
| Agent type | Examples | How it works |
|
|
74
|
+
| ----------------------- | ----------------------------------------------------- | ------------------------------------------------------- |
|
|
75
|
+
| Terminal and IDE agents | Claude Code, Codex, Cursor, GitHub Copilot in VS Code | Paste the prompt. The agent runs every command for you. |
|
|
76
|
+
| Chat-only agents | ChatGPT, Claude on the web | Ask for the commands, then run them yourself. |
|
|
77
|
+
|
|
78
|
+
### Agents that can't run commands {#chat-only-agents}
|
|
79
|
+
|
|
80
|
+
A chat-only agent can't open a terminal, so it can't create the app. Paste the prompt anyway and add this line at the top:
|
|
81
|
+
|
|
82
|
+
```markdown
|
|
83
|
+
You can't run commands. Give me each command to run in my terminal,
|
|
84
|
+
one at a time, and wait for me to tell you what happened before moving
|
|
85
|
+
on.
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The agent walks you through the same steps and helps if something goes wrong.
|
|
89
|
+
|
|
90
|
+
## Your agent and the app's agent
|
|
91
|
+
|
|
92
|
+
Two agents can work on your app, and they do different jobs.
|
|
93
|
+
|
|
94
|
+
- **Your own agent** is the one you pasted the prompt into, such as Claude Code, Codex, or Cursor. It lives outside the app, in your terminal or editor.
|
|
95
|
+
- **The app's agent** is the chat that comes built into the app. You open it in the browser while the app runs.
|
|
96
|
+
|
|
97
|
+
| | Your own agent (outside the app) | The app's agent (inside the app) |
|
|
98
|
+
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
99
|
+
| Where you use it | Your terminal or editor | The app's chat in the browser |
|
|
100
|
+
| What it works on | Your project's files | The app's data and the screen you are looking at |
|
|
101
|
+
| What it can do | Create the project, install packages, run `pnpm dev`, write and change actions, pages, and database tables, and prepare a deploy | Call the app's actions as tools, read and update the app's data, and answer questions about what is on screen |
|
|
102
|
+
| Needs an LLM connected to the app | No, it uses its own model | Yes. See [Connect an LLM](/docs/getting-started#connect-an-llm) |
|
|
103
|
+
| Best for | Building and changing the app | Using the app and doing its everyday work |
|
|
104
|
+
|
|
105
|
+
Your own agent builds the app. The app's agent runs it. When your own agent adds an action, the app's agent can call that action right away because both use the same action.
|
|
106
|
+
|
|
107
|
+
### Help your own agent build well
|
|
108
|
+
|
|
109
|
+
The generated app is set up so your own agent works well in it without extra instructions.
|
|
110
|
+
|
|
111
|
+
- `AGENTS.md` in the app root tells an agent how the app is laid out and which rules to follow.
|
|
112
|
+
- `.agents/skills/` holds short guides for common jobs, such as adding a feature or writing an action.
|
|
113
|
+
- Docs for your exact framework version are bundled with the app. An agent can search them with `pnpm action docs-search --query "<topic>"`.
|
|
114
|
+
|
|
115
|
+
When you ask for something new, you can tell your agent to read these first. For example: "Read AGENTS.md and the matching skill before you change anything."
|
|
116
|
+
|
|
117
|
+
To let your own agent use a running app's tools instead of its code, see [External Agents](/docs/external-agents).
|
|
118
|
+
|
|
119
|
+
## Ask for your first change
|
|
120
|
+
|
|
121
|
+
With the app running, ask your agent for a small change and watch it work. Here are a few examples:
|
|
122
|
+
|
|
123
|
+
```markdown
|
|
124
|
+
Add an action named list-notes that returns an empty list, then show
|
|
125
|
+
it on a new /notes page.
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```markdown
|
|
129
|
+
Change the hello action so it greets people with "Welcome" instead of
|
|
130
|
+
"Hello", then call it for Alex to confirm.
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```markdown
|
|
134
|
+
Read the docs for deploying an app, then tell me which steps I need to
|
|
135
|
+
take to put this app online.
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The agent changes the same actions that the UI and the app's own agent use. That shared action model is why one change shows up everywhere. [Key Concepts](/docs/key-concepts) explains how it fits together.
|
|
139
|
+
|
|
140
|
+
## Next steps
|
|
141
|
+
|
|
142
|
+
<Cards>
|
|
143
|
+
|
|
144
|
+
### [Getting Started](/docs/getting-started)
|
|
145
|
+
|
|
146
|
+
Create the same app by hand and learn how an action works.
|
|
147
|
+
|
|
148
|
+
### [External Agents](/docs/external-agents)
|
|
149
|
+
|
|
150
|
+
Connect Claude, ChatGPT, Codex, or Cursor to an app that is already running.
|
|
151
|
+
|
|
152
|
+
### [Actions](/docs/actions-overview)
|
|
153
|
+
|
|
154
|
+
Define the capabilities that both your UI and your agents use.
|
|
155
|
+
|
|
156
|
+
### [Deploy an app](/docs/deploy-an-app)
|
|
157
|
+
|
|
158
|
+
Move from local development to a real database and a supported host.
|
|
159
|
+
|
|
160
|
+
</Cards>
|
|
@@ -15,6 +15,9 @@ the UI calls from code.
|
|
|
15
15
|
In this guide, you'll create an agent with a chat UI and call its included
|
|
16
16
|
`hello` action from the agent and the UI.
|
|
17
17
|
|
|
18
|
+
Prefer to have an AI agent do the setup? See
|
|
19
|
+
[Create an App with an AI Agent](/docs/create-with-an-agent).
|
|
20
|
+
|
|
18
21
|
## Prerequisites
|
|
19
22
|
|
|
20
23
|
- [Node.js](https://nodejs.org) 22.22 or later
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.209.
|
|
3
|
+
"version": "0.209.1-nightly-20261015005848",
|
|
4
4
|
"description": "The agentic application framework for building autonomous agents with intuitive UIs",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -704,7 +704,7 @@
|
|
|
704
704
|
"yaml": "^2.9.0",
|
|
705
705
|
"yjs": "^13.6.33",
|
|
706
706
|
"zod": "^4.3.6",
|
|
707
|
-
"@agent-native/agentkit": "^0.209.
|
|
707
|
+
"@agent-native/agentkit": "^0.209.1-nightly-20261015005848",
|
|
708
708
|
"@agent-native/recap-cli": "^0.5.72"
|
|
709
709
|
},
|
|
710
710
|
"devDependencies": {
|
|
@@ -775,7 +775,7 @@
|
|
|
775
775
|
"xlsx": "^0.18.5"
|
|
776
776
|
},
|
|
777
777
|
"peerDependencies": {
|
|
778
|
-
"@agent-native/core-corpus": "
|
|
778
|
+
"@agent-native/core-corpus": "0.209.1-nightly-20261015005848",
|
|
779
779
|
"@ai-sdk/anthropic": ">=3",
|
|
780
780
|
"@ai-sdk/cohere": ">=3",
|
|
781
781
|
"@ai-sdk/google": ">=3",
|