@agent-native/core 0.207.0 → 0.207.1-nightly-20261011212936
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/thread-data-builder.d.ts +1 -0
- package/dist/agent/thread-data-builder.js +220 -45
- package/dist/automation/index.d.ts +1 -1
- package/dist/chat-threads/store.d.ts +1 -0
- package/dist/chat-threads/store.js +1 -0
- package/dist/cli/connect.d.ts +1 -0
- package/dist/cli/connect.js +8 -3
- package/dist/client/chat/agentkit-agent-native.js +133 -54
- package/dist/client/chat/runtime.js +61 -2
- package/dist/client/onboarding/first-run-status.js +0 -4
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/mcp/agent-connected.d.ts +13 -0
- package/dist/mcp/agent-connected.js +63 -0
- package/dist/mcp/connect-app-label.d.ts +11 -0
- package/dist/mcp/connect-app-label.js +33 -0
- package/dist/mcp/connect-route.js +41 -33
- package/dist/mcp/connect-store.d.ts +6 -1
- package/dist/mcp/connect-store.js +10 -2
- package/dist/mcp/oauth-route.js +17 -5
- package/dist/mcp/oauth-store.d.ts +2 -0
- package/dist/mcp/oauth-store.js +5 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/org/index.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
- package/dist/provider-api/actions/provider-api.d.ts +11 -11
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/server/agent-chat-plugin.js +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-default-model-route.d.ts +2 -2
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/mcp-connect-content.d.ts +1 -0
- package/dist/shared/mcp-connect-content.js +3 -0
- package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
- package/dist/tracking/registry.d.ts +1 -0
- package/dist/tracking/registry.js +1 -1
- package/package.json +4 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AgentKitRunSlotBusyError } from "@agent-native/agentkit/client";
|
|
2
|
-
import { isAgentKitProtocolVersion, parseAgentQueuedMessage, parseAgentRunOptions, parseAgentThreadSnapshot, persistableFilePart, } from "@agent-native/agentkit/protocol";
|
|
2
|
+
import { agentEventSequenceEnd, isAgentKitProtocolVersion, parseAgentQueuedMessage, parseAgentRunOptions, parseAgentThreadSnapshot, persistableFilePart, } from "@agent-native/agentkit/protocol";
|
|
3
3
|
import { projectRootAssistantMessages } from "../../agent/thread-message-projection.js";
|
|
4
4
|
import { RUN_NOT_STARTED_METADATA_KEY, retryContextFromRequest, } from "../../shared/agent-chat-run-not-started.js";
|
|
5
5
|
import { assertNoInlineImageBytes, stripInlineBytes, } from "../../shared/inline-bytes.js";
|
|
@@ -1135,77 +1135,162 @@ function persistedRetryRequestAttachments(value) {
|
|
|
1135
1135
|
];
|
|
1136
1136
|
});
|
|
1137
1137
|
}
|
|
1138
|
+
const MAX_PERSISTED_EVENT_OMISSION_RANGE = 256;
|
|
1139
|
+
const MAX_PERSISTED_EVENT_PREFIX_RANGES = 4_096;
|
|
1138
1140
|
function persistedHistoryEvents(events = []) {
|
|
1139
|
-
const
|
|
1140
|
-
|
|
1141
|
+
const persisted = [];
|
|
1142
|
+
const seenRuns = new Set();
|
|
1143
|
+
let generatedPrefixRanges = 0;
|
|
1144
|
+
const lastOmissionByRun = new Map();
|
|
1145
|
+
for (const event of events) {
|
|
1146
|
+
if (!seenRuns.has(event.runId)) {
|
|
1147
|
+
seenRuns.add(event.runId);
|
|
1148
|
+
const prefixRangeCount = Math.ceil(Math.max(0, event.sequence - 1) / MAX_PERSISTED_EVENT_OMISSION_RANGE);
|
|
1149
|
+
if (generatedPrefixRanges + prefixRangeCount >
|
|
1150
|
+
MAX_PERSISTED_EVENT_PREFIX_RANGES) {
|
|
1151
|
+
throw new RangeError("Agent chat retained history prefix exceeds the supported omission range count.");
|
|
1152
|
+
}
|
|
1153
|
+
generatedPrefixRanges += prefixRangeCount;
|
|
1154
|
+
for (let firstSequence = 1; firstSequence < event.sequence; firstSequence += MAX_PERSISTED_EVENT_OMISSION_RANGE) {
|
|
1155
|
+
const lastSequence = Math.min(event.sequence - 1, firstSequence + MAX_PERSISTED_EVENT_OMISSION_RANGE - 1);
|
|
1156
|
+
const index = persisted.length;
|
|
1157
|
+
persisted.push({
|
|
1158
|
+
id: `persisted-history-prefix:${event.runId}:${firstSequence}`,
|
|
1159
|
+
threadId: event.threadId,
|
|
1160
|
+
runId: event.runId,
|
|
1161
|
+
sequence: firstSequence,
|
|
1162
|
+
occurredAt: event.occurredAt,
|
|
1163
|
+
type: "x-agent-native.persisted-event-omission-range",
|
|
1164
|
+
payload: {
|
|
1165
|
+
lastSequence,
|
|
1166
|
+
sourceType: "runtime_history_prefix",
|
|
1167
|
+
},
|
|
1168
|
+
});
|
|
1169
|
+
lastOmissionByRun.set(event.runId, {
|
|
1170
|
+
index,
|
|
1171
|
+
firstSequence,
|
|
1172
|
+
lastSequence,
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1141
1176
|
const base = () => {
|
|
1142
|
-
const sequence = (sequenceByRun.get(event.runId) ?? 0) + 1;
|
|
1143
|
-
sequenceByRun.set(event.runId, sequence);
|
|
1144
1177
|
return {
|
|
1145
1178
|
id: event.id,
|
|
1146
1179
|
threadId: event.threadId,
|
|
1147
1180
|
runId: event.runId,
|
|
1148
|
-
sequence,
|
|
1181
|
+
sequence: event.sequence,
|
|
1149
1182
|
occurredAt: event.occurredAt,
|
|
1150
1183
|
};
|
|
1151
1184
|
};
|
|
1152
1185
|
if (event.type === "run.started") {
|
|
1153
|
-
|
|
1186
|
+
persisted.push({ ...base(), type: event.type });
|
|
1187
|
+
lastOmissionByRun.delete(event.runId);
|
|
1188
|
+
continue;
|
|
1154
1189
|
}
|
|
1155
1190
|
if (event.type === "run.completed" || event.type === "run.cancelled") {
|
|
1156
|
-
|
|
1191
|
+
persisted.push({ ...base(), type: event.type });
|
|
1192
|
+
lastOmissionByRun.delete(event.runId);
|
|
1193
|
+
continue;
|
|
1157
1194
|
}
|
|
1158
1195
|
if (event.type === "run.failed") {
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1196
|
+
persisted.push({
|
|
1197
|
+
...base(),
|
|
1198
|
+
type: event.type,
|
|
1199
|
+
error: persistedError(event.error),
|
|
1200
|
+
});
|
|
1201
|
+
lastOmissionByRun.delete(event.runId);
|
|
1202
|
+
continue;
|
|
1162
1203
|
}
|
|
1163
1204
|
if (event.type === "run.status") {
|
|
1164
|
-
|
|
1205
|
+
persisted.push({ ...base(), type: event.type, status: event.status });
|
|
1206
|
+
lastOmissionByRun.delete(event.runId);
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
if (event.type === "x-agent-native.persisted-event-omission" ||
|
|
1210
|
+
event.type === "x-agent-native.persisted-event-omission-range") {
|
|
1211
|
+
const index = persisted.length;
|
|
1212
|
+
persisted.push(event);
|
|
1213
|
+
lastOmissionByRun.set(event.runId, {
|
|
1214
|
+
index,
|
|
1215
|
+
firstSequence: event.sequence,
|
|
1216
|
+
lastSequence: agentEventSequenceEnd(event),
|
|
1217
|
+
});
|
|
1218
|
+
continue;
|
|
1165
1219
|
}
|
|
1166
1220
|
if (event.type === "activity.started" ||
|
|
1167
1221
|
event.type === "activity.updated" ||
|
|
1168
1222
|
event.type === "activity.completed") {
|
|
1169
1223
|
const activity = event.activity;
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
: {}),
|
|
1185
|
-
},
|
|
1224
|
+
persisted.push({
|
|
1225
|
+
...base(),
|
|
1226
|
+
type: event.type,
|
|
1227
|
+
activity: {
|
|
1228
|
+
id: activity.id,
|
|
1229
|
+
kind: activity.kind,
|
|
1230
|
+
label: activity.label,
|
|
1231
|
+
status: activity.status,
|
|
1232
|
+
...(activity.runId ? { runId: activity.runId } : {}),
|
|
1233
|
+
...(activity.agentId ? { agentId: activity.agentId } : {}),
|
|
1234
|
+
...(activity.startedAt ? { startedAt: activity.startedAt } : {}),
|
|
1235
|
+
...(activity.completedAt
|
|
1236
|
+
? { completedAt: activity.completedAt }
|
|
1237
|
+
: {}),
|
|
1186
1238
|
},
|
|
1187
|
-
|
|
1239
|
+
});
|
|
1240
|
+
lastOmissionByRun.delete(event.runId);
|
|
1241
|
+
continue;
|
|
1188
1242
|
}
|
|
1189
1243
|
if (event.type === "message.completed" &&
|
|
1190
1244
|
event.message.role === "assistant") {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
...(event.message.status ? { status: event.message.status } : {}),
|
|
1203
|
-
},
|
|
1245
|
+
persisted.push({
|
|
1246
|
+
...base(),
|
|
1247
|
+
type: event.type,
|
|
1248
|
+
message: {
|
|
1249
|
+
id: event.message.id,
|
|
1250
|
+
role: event.message.role,
|
|
1251
|
+
parts: event.message.parts.filter((part) => part.type === "text"),
|
|
1252
|
+
...(event.message.createdAt
|
|
1253
|
+
? { createdAt: event.message.createdAt }
|
|
1254
|
+
: {}),
|
|
1255
|
+
...(event.message.status ? { status: event.message.status } : {}),
|
|
1204
1256
|
},
|
|
1205
|
-
|
|
1257
|
+
});
|
|
1258
|
+
lastOmissionByRun.delete(event.runId);
|
|
1259
|
+
continue;
|
|
1206
1260
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1261
|
+
const previous = lastOmissionByRun.get(event.runId);
|
|
1262
|
+
if (previous &&
|
|
1263
|
+
event.sequence === previous.lastSequence + 1 &&
|
|
1264
|
+
event.sequence - previous.firstSequence + 1 <=
|
|
1265
|
+
MAX_PERSISTED_EVENT_OMISSION_RANGE) {
|
|
1266
|
+
const omitted = persisted[previous.index];
|
|
1267
|
+
if (omitted) {
|
|
1268
|
+
persisted[previous.index] = {
|
|
1269
|
+
...omitted,
|
|
1270
|
+
type: "x-agent-native.persisted-event-omission-range",
|
|
1271
|
+
payload: {
|
|
1272
|
+
...(asRecord("payload" in omitted ? omitted.payload : undefined) ??
|
|
1273
|
+
{}),
|
|
1274
|
+
lastSequence: event.sequence,
|
|
1275
|
+
},
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
previous.lastSequence = event.sequence;
|
|
1279
|
+
continue;
|
|
1280
|
+
}
|
|
1281
|
+
const index = persisted.length;
|
|
1282
|
+
persisted.push({
|
|
1283
|
+
...base(),
|
|
1284
|
+
type: "x-agent-native.persisted-event-omission",
|
|
1285
|
+
payload: { sourceType: event.type },
|
|
1286
|
+
});
|
|
1287
|
+
lastOmissionByRun.set(event.runId, {
|
|
1288
|
+
index,
|
|
1289
|
+
firstSequence: event.sequence,
|
|
1290
|
+
lastSequence: event.sequence,
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
return persisted;
|
|
1209
1294
|
}
|
|
1210
1295
|
function persistedMessages(messages) {
|
|
1211
1296
|
return messages.map((message) => ({
|
|
@@ -2339,17 +2424,11 @@ export function createAgentNativeAgentKitTransport(options = {}) {
|
|
|
2339
2424
|
}
|
|
2340
2425
|
const changedEvents = compactEvents.filter((event) => changedEventRunIds.has(event.runId));
|
|
2341
2426
|
const eventRunLastSequenceById = new Map();
|
|
2342
|
-
for (const run of input.snapshot.runs ?? []) {
|
|
2343
|
-
if (typeof run.id === "string" &&
|
|
2344
|
-
Number.isSafeInteger(run.lastSequence) &&
|
|
2345
|
-
run.lastSequence >= 0) {
|
|
2346
|
-
eventRunLastSequenceById.set(run.id, run.lastSequence);
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
2427
|
for (const event of input.snapshot.events ?? []) {
|
|
2350
2428
|
const previousSequence = eventRunLastSequenceById.get(event.runId) ?? 0;
|
|
2351
|
-
|
|
2352
|
-
|
|
2429
|
+
const sequenceEnd = agentEventSequenceEnd(event);
|
|
2430
|
+
if (Number.isSafeInteger(sequenceEnd)) {
|
|
2431
|
+
eventRunLastSequenceById.set(event.runId, Math.max(previousSequence, sequenceEnd));
|
|
2353
2432
|
}
|
|
2354
2433
|
}
|
|
2355
2434
|
const changedEventsByRun = new Map();
|
|
@@ -770,6 +770,53 @@ function isSyntheticToolHistoryOmissionMessage(message) {
|
|
|
770
770
|
(message.id === AGENTKIT_TOOL_HISTORY_OMISSION_MESSAGE_ID_PREFIX ||
|
|
771
771
|
/^agentkit-tool-history-omission-\d+$/.test(message.id)));
|
|
772
772
|
}
|
|
773
|
+
function equivalentRuntimeContent(left, right) {
|
|
774
|
+
let remaining = 4096;
|
|
775
|
+
const leftAncestors = new WeakSet();
|
|
776
|
+
const rightAncestors = new WeakSet();
|
|
777
|
+
const compare = (a, b, depth) => {
|
|
778
|
+
if (--remaining < 0 || depth > 64)
|
|
779
|
+
return false;
|
|
780
|
+
if (Object.is(a, b))
|
|
781
|
+
return true;
|
|
782
|
+
if (!a || !b || typeof a !== "object" || typeof b !== "object")
|
|
783
|
+
return false;
|
|
784
|
+
const prototype = Object.getPrototypeOf(a);
|
|
785
|
+
if (prototype !== Object.getPrototypeOf(b) ||
|
|
786
|
+
(prototype !== Object.prototype &&
|
|
787
|
+
prototype !== Array.prototype &&
|
|
788
|
+
prototype !== null) ||
|
|
789
|
+
leftAncestors.has(a) ||
|
|
790
|
+
rightAncestors.has(b))
|
|
791
|
+
return false;
|
|
792
|
+
if (Array.isArray(a) !== Array.isArray(b))
|
|
793
|
+
return false;
|
|
794
|
+
if (Array.isArray(a) && (!Array.isArray(b) || a.length !== b.length))
|
|
795
|
+
return false;
|
|
796
|
+
const keys = Object.keys(a);
|
|
797
|
+
if (keys.length > remaining || keys.length !== Object.keys(b).length)
|
|
798
|
+
return false;
|
|
799
|
+
leftAncestors.add(a);
|
|
800
|
+
rightAncestors.add(b);
|
|
801
|
+
try {
|
|
802
|
+
return keys.every((key) => {
|
|
803
|
+
const leftValue = Object.getOwnPropertyDescriptor(a, key);
|
|
804
|
+
const rightValue = Object.getOwnPropertyDescriptor(b, key);
|
|
805
|
+
return Boolean(leftValue &&
|
|
806
|
+
rightValue &&
|
|
807
|
+
rightValue.enumerable &&
|
|
808
|
+
"value" in leftValue &&
|
|
809
|
+
"value" in rightValue &&
|
|
810
|
+
compare(leftValue.value, rightValue.value, depth + 1));
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
finally {
|
|
814
|
+
leftAncestors.delete(a);
|
|
815
|
+
rightAncestors.delete(b);
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
return compare(left, right, 0);
|
|
819
|
+
}
|
|
773
820
|
function priorNativeHistoryMessages(messages, currentPrompt) {
|
|
774
821
|
const source = messages ?? [];
|
|
775
822
|
let currentPromptMessageIndex;
|
|
@@ -2864,6 +2911,18 @@ export function createAgentNativeChatRuntime(options = {}) {
|
|
|
2864
2911
|
const latestUserMessage = [...(turn.messages ?? [])]
|
|
2865
2912
|
.reverse()
|
|
2866
2913
|
.find((message) => message.role === "user");
|
|
2914
|
+
const latestUserMessageIndex = latestUserMessage
|
|
2915
|
+
? (turn.messages?.lastIndexOf(latestUserMessage) ?? -1)
|
|
2916
|
+
: -1;
|
|
2917
|
+
const messagesForHistory = latestUserMessage &&
|
|
2918
|
+
typeof latestUserMessage.id === "string" &&
|
|
2919
|
+
latestUserMessage.id.trim().length > 0 &&
|
|
2920
|
+
latestUserMessageIndex >= 0
|
|
2921
|
+
? turn.messages.filter((message, index) => message.role !== "user" ||
|
|
2922
|
+
message.id !== latestUserMessage.id ||
|
|
2923
|
+
!equivalentRuntimeContent(message.content, latestUserMessage.content) ||
|
|
2924
|
+
index === latestUserMessageIndex)
|
|
2925
|
+
: turn.messages;
|
|
2867
2926
|
const prompt = turn.prompt ??
|
|
2868
2927
|
latestUserMessage?.content
|
|
2869
2928
|
.map((part) => (part.type === "text" ? part.text : ""))
|
|
@@ -2884,12 +2943,12 @@ export function createAgentNativeChatRuntime(options = {}) {
|
|
|
2884
2943
|
? turnEngine
|
|
2885
2944
|
: options.engine;
|
|
2886
2945
|
const currentAttachmentUrlSet = currentTurnAttachmentUrls(turn.attachments);
|
|
2887
|
-
const history = nativeHistoryFromMessages(
|
|
2946
|
+
const history = nativeHistoryFromMessages(messagesForHistory, prompt, currentAttachmentUrlSet);
|
|
2888
2947
|
const loadedSkillSlugs = loadedSkillSlugsFromMessages(turn.messages);
|
|
2889
2948
|
const pendingApprovalHistory = approvedToolCalls && continuationMessageState
|
|
2890
2949
|
? pendingApprovalRuntimeMessages(continuationMessageState)
|
|
2891
2950
|
: { messages: [], omitted: false, toolHistoryOmitted: false };
|
|
2892
|
-
const structuredHistory = nativeStructuredHistoryFromMessages(
|
|
2951
|
+
const structuredHistory = nativeStructuredHistoryFromMessages(messagesForHistory, prompt, currentAttachmentUrlSet, pendingApprovalHistory.messages, pendingApprovalHistory.omitted, pendingApprovalHistory.toolHistoryOmitted);
|
|
2893
2952
|
return {
|
|
2894
2953
|
message: prompt,
|
|
2895
2954
|
...(latestUserMessage?.id
|
|
@@ -55,10 +55,6 @@ async function requestFirstRunOnboardingStatus() {
|
|
|
55
55
|
dispatchFirstRunOnboardingStatus(firstRun);
|
|
56
56
|
return firstRun;
|
|
57
57
|
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
dispatchFirstRunOnboardingStatus(false);
|
|
60
|
-
throw error;
|
|
61
|
-
}
|
|
62
58
|
finally {
|
|
63
59
|
if (timeoutId !== undefined)
|
|
64
60
|
clearTimeout(timeoutId);
|
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
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function connectionApp(origin: string, appId?: string): string;
|
|
2
|
+
/**
|
|
3
|
+
* The connect CLI sends the clients it sets up as comma-joined client ids.
|
|
4
|
+
* Any other device client value, such as "Example, Inc.", is one name.
|
|
5
|
+
*/
|
|
6
|
+
export declare function deviceFlowClients(client: string | null): string | string[] | null;
|
|
7
|
+
export declare function trackAgentConnected(input: {
|
|
8
|
+
email: string;
|
|
9
|
+
origin: string;
|
|
10
|
+
appId?: string;
|
|
11
|
+
client: string | readonly string[] | null;
|
|
12
|
+
method: "mcp" | "oauth";
|
|
13
|
+
}): Promise<"emitted" | "duplicate" | "disabled" | "failed">;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { CLIENTS } from "../cli/mcp-config-writers.js";
|
|
3
|
+
import { mutateSetting } from "../settings/store.js";
|
|
4
|
+
import { normalizeTrackingDimension } from "../shared/analytics-events.js";
|
|
5
|
+
import { resolveMcpConnectHostId } from "../shared/mcp-connect-content.js";
|
|
6
|
+
import { isTrackingSuppressed, listTrackingProviders, track, } from "../tracking/registry.js";
|
|
7
|
+
import { detectVendorClient } from "./analytics.js";
|
|
8
|
+
import { connectAppLabel, connectEnvironment } from "./connect-app-label.js";
|
|
9
|
+
export function connectionApp(origin, appId) {
|
|
10
|
+
return normalizeTrackingDimension(connectAppLabel(origin, { appId }, connectEnvironment()));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The connect CLI sends the clients it sets up as comma-joined client ids.
|
|
14
|
+
* Any other device client value, such as "Example, Inc.", is one name.
|
|
15
|
+
*/
|
|
16
|
+
export function deviceFlowClients(client) {
|
|
17
|
+
const ids = client?.split(",");
|
|
18
|
+
return ids?.every((id) => CLIENTS.includes(id)) ? ids : client;
|
|
19
|
+
}
|
|
20
|
+
export async function trackAgentConnected(input) {
|
|
21
|
+
// Everything runs inside the try: the connection is already committed, so a
|
|
22
|
+
// tracking or configuration failure must not turn it into a failed response.
|
|
23
|
+
try {
|
|
24
|
+
if (listTrackingProviders().length === 0 ||
|
|
25
|
+
isTrackingSuppressed(input.email))
|
|
26
|
+
return "disabled";
|
|
27
|
+
const app = connectionApp(input.origin, input.appId);
|
|
28
|
+
const raw = input.client;
|
|
29
|
+
const clients = new Set((typeof raw === "string" ? [raw] : (raw ?? ["unknown"])).map((name) => resolveMcpConnectHostId(name) ??
|
|
30
|
+
detectVendorClient(name) ??
|
|
31
|
+
(name.trim().toLowerCase().slice(0, 120) || "unknown")));
|
|
32
|
+
let emitted = false;
|
|
33
|
+
for (const client of clients) {
|
|
34
|
+
const email = input.email.trim().toLowerCase();
|
|
35
|
+
const key = createHash("sha256")
|
|
36
|
+
.update(JSON.stringify([email, app, client]))
|
|
37
|
+
.digest("hex");
|
|
38
|
+
const claimId = randomUUID();
|
|
39
|
+
// Both success paths share a durable CAS claim; process-local state
|
|
40
|
+
// cannot suppress reconnects across serverless invocations.
|
|
41
|
+
const claim = await mutateSetting(`agent-connected:${key}`, (current) => {
|
|
42
|
+
if (current && typeof current.claim_id !== "string") {
|
|
43
|
+
throw new Error("Invalid agent connection claim");
|
|
44
|
+
}
|
|
45
|
+
return current ?? { claim_id: claimId };
|
|
46
|
+
});
|
|
47
|
+
if (claim.claim_id !== claimId)
|
|
48
|
+
continue;
|
|
49
|
+
track("agent_connected", {
|
|
50
|
+
email,
|
|
51
|
+
app,
|
|
52
|
+
client,
|
|
53
|
+
connection_method: input.method,
|
|
54
|
+
}, { userId: email });
|
|
55
|
+
emitted = true;
|
|
56
|
+
}
|
|
57
|
+
return emitted ? "emitted" : "duplicate";
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error("[agent-connected] Failed to record connection:", error);
|
|
61
|
+
return "failed";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { McpConnectEnvironment } from "../shared/mcp-connect-content.js";
|
|
2
|
+
export declare function connectEnvironment(): McpConnectEnvironment;
|
|
3
|
+
/**
|
|
4
|
+
* The app a connection belongs to. The connect identity names its MCP server
|
|
5
|
+
* from this and the `agent_connected` event reports it, so the two cannot
|
|
6
|
+
* disagree about which app a client connected to.
|
|
7
|
+
*/
|
|
8
|
+
export declare function connectAppLabel(origin: string, options: {
|
|
9
|
+
appId?: string;
|
|
10
|
+
appName?: string;
|
|
11
|
+
}, environment: McpConnectEnvironment): string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getAppConfig } from "../app-config/index.js";
|
|
2
|
+
import { isAgentNativeDeploymentEnvironment } from "../config.js";
|
|
3
|
+
import { resolveDeployEnvironment } from "../server/deploy-environment.js";
|
|
4
|
+
export function connectEnvironment() {
|
|
5
|
+
const environment = resolveDeployEnvironment();
|
|
6
|
+
if (!isAgentNativeDeploymentEnvironment(environment)) {
|
|
7
|
+
throw new Error(`Unknown deployment environment "${environment}"`);
|
|
8
|
+
}
|
|
9
|
+
return environment;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The app a connection belongs to. The connect identity names its MCP server
|
|
13
|
+
* from this and the `agent_connected` event reports it, so the two cannot
|
|
14
|
+
* disagree about which app a client connected to.
|
|
15
|
+
*/
|
|
16
|
+
export function connectAppLabel(origin, options, environment) {
|
|
17
|
+
const app = getAppConfig().app;
|
|
18
|
+
const declared = options.appId ?? app.id ?? app.template ?? app.slug;
|
|
19
|
+
if (declared)
|
|
20
|
+
return declared;
|
|
21
|
+
// Deployed functions run without npm's `npm_package_name`, so first-party
|
|
22
|
+
// apps usually land here. A beta lane is `beta.<app>.agent-native.com`: its
|
|
23
|
+
// leading label names the lane, and every beta app would share it. Only a
|
|
24
|
+
// beta deployment drops it, so no production name changes.
|
|
25
|
+
try {
|
|
26
|
+
const host = new URL(origin).hostname;
|
|
27
|
+
const h = environment === "beta" ? host.replace(/^beta\./, "") : host;
|
|
28
|
+
return h.split(".")[0] || h;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return options.appName || "app";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getMethod, getHeader } from "h3";
|
|
3
|
-
import { getAppConfig } from "../app-config/index.js";
|
|
4
|
-
import { isAgentNativeDeploymentEnvironment } from "../config.js";
|
|
5
3
|
import { mcpSettingsMessagesForLocale } from "../localization/mcp-settings-messages.js";
|
|
6
4
|
import { resolveLocaleFromRequest } from "../localization/server.js";
|
|
7
5
|
import { localeDirection, normalizeLocaleCode, } from "../localization/shared.js";
|
|
@@ -10,11 +8,12 @@ import { getConfiguredAppBasePath } from "../server/app-base-path.js";
|
|
|
10
8
|
import { getSession, getConfiguredLoginHtml, isLoopbackRequest, } from "../server/auth.js";
|
|
11
9
|
import { CREDENTIAL_MEMBERSHIP_UNAVAILABLE_MESSAGE } from "../server/credential-membership-unavailable.js";
|
|
12
10
|
import { readDeployCredentialEnv } from "../server/credential-provider.js";
|
|
13
|
-
import { resolveDeployEnvironment } from "../server/deploy-environment.js";
|
|
14
11
|
import { publicFrameworkPath } from "../server/framework-route-prefix.js";
|
|
15
12
|
import { readBody } from "../server/h3-helpers.js";
|
|
16
13
|
import { FRAMEWORK_INTERNAL_ROUTE_PREFIX } from "../shared/framework-route-prefix.js";
|
|
17
14
|
import { buildMcpInstallLink, derivedMcpServerBaseName, getMcpConnectGuides, getMcpStaticTokenFallback, interpolateMcpConnectTemplate, mcpConnectServerName, resolveMcpConnectGuideId, } from "../shared/mcp-connect-content.js";
|
|
15
|
+
import { deviceFlowClients, trackAgentConnected } from "./agent-connected.js";
|
|
16
|
+
import { connectAppLabel, connectEnvironment } from "./connect-app-label.js";
|
|
18
17
|
import { ensureConnectTables, recordMintedToken, listTokens, revokeToken, normalizeServiceName, serviceIdentityEmail, createDeviceCode, getDeviceCode, getDeviceCodeByUserCode, approveDeviceCode, consumeDeviceCode, claimDeviceCodeForMint, finishDeviceCodeMint, expireDeviceCode, MCP_CONNECT_OAUTH_CLIENT_ID, DEFAULT_TOKEN_TTL_DAYS, MIN_TOKEN_TTL_DAYS, MAX_TOKEN_TTL_DAYS, MAX_SERVICE_TOKEN_TTL_DAYS, DEVICE_CODE_TTL_MS, } from "./connect-store.js";
|
|
19
18
|
import { McpCredentialIssuanceError, withMcpCredentialIssuance, } from "./credential-issuance.js";
|
|
20
19
|
import { getMcpOAuthIssuer, resolveMcpOAuthIssuer } from "./oauth-route.js";
|
|
@@ -78,31 +77,6 @@ function joinAppPath(basePath, path) {
|
|
|
78
77
|
return basePath;
|
|
79
78
|
return `${basePath}${path.startsWith("/") ? path : `/${path}`}`;
|
|
80
79
|
}
|
|
81
|
-
function appLabel(origin, options, environment) {
|
|
82
|
-
const app = getAppConfig().app;
|
|
83
|
-
const declared = options.appId ?? app.id ?? app.template ?? app.slug;
|
|
84
|
-
if (declared)
|
|
85
|
-
return declared;
|
|
86
|
-
// Deployed functions run without npm's `npm_package_name`, so first-party
|
|
87
|
-
// apps usually land here. A beta lane is `beta.<app>.agent-native.com`: its
|
|
88
|
-
// leading label names the lane, and every beta app would share it. Only a
|
|
89
|
-
// beta deployment drops it, so no production name changes.
|
|
90
|
-
try {
|
|
91
|
-
const host = new URL(origin).hostname;
|
|
92
|
-
const h = environment === "beta" ? host.replace(/^beta\./, "") : host;
|
|
93
|
-
return h.split(".")[0] || h;
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return options.appName || "app";
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function connectEnvironment() {
|
|
100
|
-
const environment = resolveDeployEnvironment();
|
|
101
|
-
if (!isAgentNativeDeploymentEnvironment(environment)) {
|
|
102
|
-
throw new Error(`Unknown deployment environment "${environment}"`);
|
|
103
|
-
}
|
|
104
|
-
return environment;
|
|
105
|
-
}
|
|
106
80
|
/**
|
|
107
81
|
* The one name and URL every connect surface hands to an MCP client: this
|
|
108
82
|
* page, the device-flow payload, the settings tab, share dialogs (through the
|
|
@@ -111,10 +85,10 @@ function connectEnvironment() {
|
|
|
111
85
|
export function resolveMcpConnectIdentity(appUrl, options) {
|
|
112
86
|
const environment = connectEnvironment();
|
|
113
87
|
const baseName = options.serverName?.trim() ||
|
|
114
|
-
derivedMcpServerBaseName(
|
|
88
|
+
derivedMcpServerBaseName(connectAppLabel(appUrl, options, environment), environment);
|
|
115
89
|
return {
|
|
116
90
|
serverName: mcpConnectServerName(baseName, environment),
|
|
117
|
-
appName: options.appName ||
|
|
91
|
+
appName: options.appName || connectAppLabel(appUrl, options, environment),
|
|
118
92
|
appUrl,
|
|
119
93
|
mcpUrl: mcpResourceUrl(appUrl),
|
|
120
94
|
environment,
|
|
@@ -1217,7 +1191,8 @@ function renderConnectPage(params) {
|
|
|
1217
1191
|
var ttlEl = document.getElementById("ttl");
|
|
1218
1192
|
var label = labelEl ? labelEl.value || undefined : undefined;
|
|
1219
1193
|
var ttlDays = ttlEl ? parseInt(ttlEl.value, 10) || undefined : undefined;
|
|
1220
|
-
var
|
|
1194
|
+
var activeGuide = document.querySelector('[data-tab].is-active');
|
|
1195
|
+
var m = await postJson("/token", { label: label, ttlDays: ttlDays, client: activeGuide ? activeGuide.getAttribute("data-tab") : undefined });
|
|
1221
1196
|
if (!m.ok) {
|
|
1222
1197
|
resetButtonLoading(btn);
|
|
1223
1198
|
showMsg(COPY.couldNotCreate);
|
|
@@ -1350,6 +1325,13 @@ export async function handleMcpConnect(event, subpath, options = {}) {
|
|
|
1350
1325
|
requestOrigin: origin,
|
|
1351
1326
|
...(catalogScope ? { catalogScope } : {}),
|
|
1352
1327
|
});
|
|
1328
|
+
await trackAgentConnected({
|
|
1329
|
+
email: session.email,
|
|
1330
|
+
origin: issuer,
|
|
1331
|
+
appId: options.appId,
|
|
1332
|
+
client: typeof body.client === "string" ? body.client : null,
|
|
1333
|
+
method: "mcp",
|
|
1334
|
+
});
|
|
1353
1335
|
return json(mcpResultPayload(identity, { token }));
|
|
1354
1336
|
}
|
|
1355
1337
|
catch (err) {
|
|
@@ -1378,7 +1360,21 @@ export async function handleMcpConnect(event, subpath, options = {}) {
|
|
|
1378
1360
|
typeof body.fullCatalog !== "boolean") {
|
|
1379
1361
|
return json({ error: "fullCatalog must be a boolean." }, 400);
|
|
1380
1362
|
}
|
|
1381
|
-
|
|
1363
|
+
if (body.client !== undefined &&
|
|
1364
|
+
(typeof body.client !== "string" || body.client.length > 500)) {
|
|
1365
|
+
return json({ error: "Invalid request body." }, 400);
|
|
1366
|
+
}
|
|
1367
|
+
if (body.purpose !== undefined &&
|
|
1368
|
+
body.purpose !== "agent" &&
|
|
1369
|
+
body.purpose !== "credential") {
|
|
1370
|
+
return json({ error: "Invalid request body." }, 400);
|
|
1371
|
+
}
|
|
1372
|
+
const row = await createDeviceCode(body.fullCatalog === true ? "full" : null, {
|
|
1373
|
+
...(typeof body.client === "string" ? { client: body.client } : {}),
|
|
1374
|
+
...(body.purpose === "agent" || body.purpose === "credential"
|
|
1375
|
+
? { purpose: body.purpose }
|
|
1376
|
+
: {}),
|
|
1377
|
+
});
|
|
1382
1378
|
const verificationUri = `${issuer}${MCP_PUBLIC_ROUTE_PREFIX}/connect`;
|
|
1383
1379
|
return json({
|
|
1384
1380
|
device_code: row.deviceCode,
|
|
@@ -1468,7 +1464,8 @@ export async function handleMcpConnect(event, subpath, options = {}) {
|
|
|
1468
1464
|
canUseDevOpenConnect(event);
|
|
1469
1465
|
const orgDomain = await resolveOrgDomain(row.orgId ?? undefined);
|
|
1470
1466
|
await prepareConnectIssuance();
|
|
1471
|
-
|
|
1467
|
+
let connected = false;
|
|
1468
|
+
const response = await withMcpCredentialIssuance({
|
|
1472
1469
|
email: row.ownerEmail,
|
|
1473
1470
|
orgId: row.orgId,
|
|
1474
1471
|
requestOrigin: origin,
|
|
@@ -1519,11 +1516,22 @@ export async function handleMcpConnect(event, subpath, options = {}) {
|
|
|
1519
1516
|
if (!(await finishDeviceCodeMint(deviceCode, jti, tx))) {
|
|
1520
1517
|
throw new McpCredentialIssuanceError("unavailable");
|
|
1521
1518
|
}
|
|
1519
|
+
connected = true;
|
|
1522
1520
|
return json({
|
|
1523
1521
|
status: "approved",
|
|
1524
1522
|
...mcpResultPayload(identity, { token }),
|
|
1525
1523
|
});
|
|
1526
1524
|
});
|
|
1525
|
+
if (connected && row.purpose !== "credential") {
|
|
1526
|
+
await trackAgentConnected({
|
|
1527
|
+
email: row.ownerEmail,
|
|
1528
|
+
origin: issuer,
|
|
1529
|
+
appId: options.appId,
|
|
1530
|
+
client: deviceFlowClients(row.client ?? null),
|
|
1531
|
+
method: "mcp",
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
return response;
|
|
1527
1535
|
}
|
|
1528
1536
|
catch (err) {
|
|
1529
1537
|
if (err instanceof McpCredentialIssuanceError)
|
|
@@ -109,11 +109,16 @@ export interface DeviceCodeRow {
|
|
|
109
109
|
status: "pending" | "approved" | "minting" | "consumed" | "expired";
|
|
110
110
|
tokenJti: string | null;
|
|
111
111
|
catalogScope: "full" | null;
|
|
112
|
+
client?: string | null;
|
|
113
|
+
purpose?: "agent" | "credential" | null;
|
|
112
114
|
createdAt: number | null;
|
|
113
115
|
expiresAt: number | null;
|
|
114
116
|
consumedAt: number | null;
|
|
115
117
|
}
|
|
116
|
-
export declare function createDeviceCode(catalogScope?: "full" | null
|
|
118
|
+
export declare function createDeviceCode(catalogScope?: "full" | null, connection?: {
|
|
119
|
+
client?: string;
|
|
120
|
+
purpose?: "agent" | "credential";
|
|
121
|
+
}): Promise<DeviceCodeRow>;
|
|
117
122
|
export declare function getDeviceCode(deviceCode: string, exec?: DbExec): Promise<DeviceCodeRow | null>;
|
|
118
123
|
export declare function getDeviceCodeByUserCode(userCode: string, exec?: DbExec): Promise<DeviceCodeRow | null>;
|
|
119
124
|
export declare function approveDeviceCode(userCode: string, ownerEmail: string, orgId: string | null, exec?: DbExec): Promise<DeviceCodeRow | "not_found" | "expired" | "already">;
|