aui-agent-builder 0.4.0-alpha.0 → 0.4.0
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/LICENSE +1 -2
- package/README.md +608 -114
- package/dist/api-client/apollo-client.d.ts +427 -0
- package/dist/api-client/apollo-client.d.ts.map +1 -0
- package/dist/api-client/apollo-client.js +347 -0
- package/dist/api-client/apollo-client.js.map +1 -0
- package/dist/api-client/index.d.ts +541 -21
- package/dist/api-client/index.d.ts.map +1 -1
- package/dist/api-client/index.js +956 -50
- package/dist/api-client/index.js.map +1 -1
- package/dist/api-client/kb-view-client.d.ts +94 -19
- package/dist/api-client/kb-view-client.d.ts.map +1 -1
- package/dist/api-client/kb-view-client.js +332 -35
- package/dist/api-client/kb-view-client.js.map +1 -1
- package/dist/api-client/rag-client.d.ts +28 -60
- package/dist/api-client/rag-client.d.ts.map +1 -1
- package/dist/api-client/rag-client.js +46 -42
- package/dist/api-client/rag-client.js.map +1 -1
- package/dist/commands/account.d.ts +11 -4
- package/dist/commands/account.d.ts.map +1 -1
- package/dist/commands/account.js +76 -59
- package/dist/commands/account.js.map +1 -1
- package/dist/commands/agents.d.ts +66 -2
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +706 -92
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/apollo.d.ts +185 -0
- package/dist/commands/apollo.d.ts.map +1 -0
- package/dist/commands/apollo.js +682 -0
- package/dist/commands/apollo.js.map +1 -0
- package/dist/commands/env.js +1 -1
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/import-agent.d.ts +31 -0
- package/dist/commands/import-agent.d.ts.map +1 -1
- package/dist/commands/import-agent.js +1068 -283
- package/dist/commands/import-agent.js.map +1 -1
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +3 -3
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +403 -94
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/integration-mcp-test.d.ts +68 -0
- package/dist/commands/integration-mcp-test.d.ts.map +1 -0
- package/dist/commands/integration-mcp-test.js +885 -0
- package/dist/commands/integration-mcp-test.js.map +1 -0
- package/dist/commands/integration-mcp-url.d.ts +40 -0
- package/dist/commands/integration-mcp-url.d.ts.map +1 -0
- package/dist/commands/integration-mcp-url.js +162 -0
- package/dist/commands/integration-mcp-url.js.map +1 -0
- package/dist/commands/integration-test.d.ts +108 -0
- package/dist/commands/integration-test.d.ts.map +1 -0
- package/dist/commands/integration-test.js +251 -0
- package/dist/commands/integration-test.js.map +1 -0
- package/dist/commands/integration-toolkits.d.ts +35 -0
- package/dist/commands/integration-toolkits.d.ts.map +1 -0
- package/dist/commands/integration-toolkits.js +101 -0
- package/dist/commands/integration-toolkits.js.map +1 -0
- package/dist/commands/integration-tools.d.ts +34 -0
- package/dist/commands/integration-tools.d.ts.map +1 -0
- package/dist/commands/integration-tools.js +108 -0
- package/dist/commands/integration-tools.js.map +1 -0
- package/dist/commands/integration.d.ts +83 -9
- package/dist/commands/integration.d.ts.map +1 -1
- package/dist/commands/integration.js +700 -252
- package/dist/commands/integration.js.map +1 -1
- package/dist/commands/legacy/push-records-mode.d.ts +166 -0
- package/dist/commands/legacy/push-records-mode.d.ts.map +1 -0
- package/dist/commands/legacy/push-records-mode.js +2621 -0
- package/dist/commands/legacy/push-records-mode.js.map +1 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +34 -5
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/pull-agent.d.ts +10 -0
- package/dist/commands/pull-agent.d.ts.map +1 -1
- package/dist/commands/pull-agent.js +713 -178
- package/dist/commands/pull-agent.js.map +1 -1
- package/dist/commands/push.d.ts +91 -6
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +1514 -1144
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/rag.d.ts +1 -0
- package/dist/commands/rag.d.ts.map +1 -1
- package/dist/commands/rag.js +92 -36
- package/dist/commands/rag.js.map +1 -1
- package/dist/commands/report.d.ts +138 -0
- package/dist/commands/report.d.ts.map +1 -0
- package/dist/commands/report.js +205 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +1 -4
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/sync-session.d.ts +13 -0
- package/dist/commands/sync-session.d.ts.map +1 -0
- package/dist/commands/sync-session.js +56 -0
- package/dist/commands/sync-session.js.map +1 -0
- package/dist/commands/upgrade.d.ts.map +1 -1
- package/dist/commands/upgrade.js +1 -1
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/commands/util/agent-mode.d.ts +69 -0
- package/dist/commands/util/agent-mode.d.ts.map +1 -0
- package/dist/commands/util/agent-mode.js +159 -0
- package/dist/commands/util/agent-mode.js.map +1 -0
- package/dist/commands/util/agent-resolve.d.ts +102 -0
- package/dist/commands/util/agent-resolve.d.ts.map +1 -0
- package/dist/commands/util/agent-resolve.js +148 -0
- package/dist/commands/util/agent-resolve.js.map +1 -0
- package/dist/commands/util/apollo-agent.d.ts +70 -0
- package/dist/commands/util/apollo-agent.d.ts.map +1 -0
- package/dist/commands/util/apollo-agent.js +100 -0
- package/dist/commands/util/apollo-agent.js.map +1 -0
- package/dist/commands/validate.d.ts +129 -9
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +666 -804
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/version-snapshot.d.ts +21 -0
- package/dist/commands/version-snapshot.d.ts.map +1 -0
- package/dist/commands/version-snapshot.js +948 -0
- package/dist/commands/version-snapshot.js.map +1 -0
- package/dist/commands/version.d.ts +15 -2
- package/dist/commands/version.d.ts.map +1 -1
- package/dist/commands/version.js +439 -206
- package/dist/commands/version.js.map +1 -1
- package/dist/config/index.d.ts +96 -5
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +97 -59
- package/dist/config/index.js.map +1 -1
- package/dist/errors/index.d.ts +1 -12
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +90 -8
- package/dist/errors/index.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1066 -174
- package/dist/index.js.map +1 -1
- package/dist/services/account.service.js +1 -1
- package/dist/services/account.service.js.map +1 -1
- package/dist/services/agents.service.d.ts.map +1 -1
- package/dist/services/agents.service.js +11 -5
- package/dist/services/agents.service.js.map +1 -1
- package/dist/services/auth.service.d.ts +78 -0
- package/dist/services/auth.service.d.ts.map +1 -1
- package/dist/services/auth.service.js +290 -16
- package/dist/services/auth.service.js.map +1 -1
- package/dist/services/integration.service.d.ts +374 -22
- package/dist/services/integration.service.d.ts.map +1 -1
- package/dist/services/integration.service.js +643 -95
- package/dist/services/integration.service.js.map +1 -1
- package/dist/services/kb-view.service.d.ts +17 -30
- package/dist/services/kb-view.service.d.ts.map +1 -1
- package/dist/services/kb-view.service.js +191 -118
- package/dist/services/kb-view.service.js.map +1 -1
- package/dist/services/list-agents.service.d.ts +10 -0
- package/dist/services/list-agents.service.d.ts.map +1 -1
- package/dist/services/list-agents.service.js +16 -21
- package/dist/services/list-agents.service.js.map +1 -1
- package/dist/services/pull-schema.service.d.ts +20 -1
- package/dist/services/pull-schema.service.d.ts.map +1 -1
- package/dist/services/pull-schema.service.js +313 -121
- package/dist/services/pull-schema.service.js.map +1 -1
- package/dist/services/rag.service.d.ts +21 -8
- package/dist/services/rag.service.d.ts.map +1 -1
- package/dist/services/rag.service.js +43 -16
- package/dist/services/rag.service.js.map +1 -1
- package/dist/services/status.service.d.ts +8 -0
- package/dist/services/status.service.d.ts.map +1 -1
- package/dist/services/status.service.js +16 -1
- package/dist/services/status.service.js.map +1 -1
- package/dist/services/sync-session.service.d.ts +38 -0
- package/dist/services/sync-session.service.d.ts.map +1 -0
- package/dist/services/sync-session.service.js +93 -0
- package/dist/services/sync-session.service.js.map +1 -0
- package/dist/telemetry.d.ts +169 -2
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +878 -4
- package/dist/telemetry.js.map +1 -1
- package/dist/types/entity.d.ts +21 -0
- package/dist/types/entity.d.ts.map +1 -1
- package/dist/ui/components/Banner.js +1 -1
- package/dist/ui/components/Banner.js.map +1 -1
- package/dist/ui/components/EnvironmentBadge.d.ts +8 -0
- package/dist/ui/components/EnvironmentBadge.d.ts.map +1 -0
- package/dist/ui/components/EnvironmentBadge.js +11 -0
- package/dist/ui/components/EnvironmentBadge.js.map +1 -0
- package/dist/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.d.ts.map +1 -1
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/index.js.map +1 -1
- package/dist/ui/theme.d.ts +13 -0
- package/dist/ui/theme.d.ts.map +1 -1
- package/dist/ui/theme.js +12 -0
- package/dist/ui/theme.js.map +1 -1
- package/dist/ui/views/AccountView.js +1 -1
- package/dist/ui/views/AccountView.js.map +1 -1
- package/dist/ui/views/EnvView.d.ts.map +1 -1
- package/dist/ui/views/EnvView.js +2 -2
- package/dist/ui/views/EnvView.js.map +1 -1
- package/dist/ui/views/ImportAgentView.d.ts +15 -0
- package/dist/ui/views/ImportAgentView.d.ts.map +1 -1
- package/dist/ui/views/ImportAgentView.js +8 -3
- package/dist/ui/views/ImportAgentView.js.map +1 -1
- package/dist/ui/views/IntegrationView.d.ts +3 -1
- package/dist/ui/views/IntegrationView.d.ts.map +1 -1
- package/dist/ui/views/IntegrationView.js +2 -2
- package/dist/ui/views/IntegrationView.js.map +1 -1
- package/dist/ui/views/LoginView.d.ts +2 -1
- package/dist/ui/views/LoginView.d.ts.map +1 -1
- package/dist/ui/views/LoginView.js +20 -14
- package/dist/ui/views/LoginView.js.map +1 -1
- package/dist/ui/views/PushView.d.ts +3 -1
- package/dist/ui/views/PushView.d.ts.map +1 -1
- package/dist/ui/views/PushView.js +8 -2
- package/dist/ui/views/PushView.js.map +1 -1
- package/dist/ui/views/RagView.d.ts +6 -1
- package/dist/ui/views/RagView.d.ts.map +1 -1
- package/dist/ui/views/RagView.js +23 -0
- package/dist/ui/views/RagView.js.map +1 -1
- package/dist/ui/views/StatusView.js +4 -4
- package/dist/ui/views/StatusView.js.map +1 -1
- package/dist/ui/views/SyncSessionView.d.ts +7 -0
- package/dist/ui/views/SyncSessionView.d.ts.map +1 -0
- package/dist/ui/views/SyncSessionView.js +10 -0
- package/dist/ui/views/SyncSessionView.js.map +1 -0
- package/dist/utils/agent-injection.d.ts +49 -0
- package/dist/utils/agent-injection.d.ts.map +1 -0
- package/dist/utils/agent-injection.js +83 -0
- package/dist/utils/agent-injection.js.map +1 -0
- package/dist/utils/fetch-with-timeout.d.ts +57 -0
- package/dist/utils/fetch-with-timeout.d.ts.map +1 -0
- package/dist/utils/fetch-with-timeout.js +125 -0
- package/dist/utils/fetch-with-timeout.js.map +1 -0
- package/dist/utils/help-json.d.ts +44 -0
- package/dist/utils/help-json.d.ts.map +1 -0
- package/dist/utils/help-json.js +62 -0
- package/dist/utils/help-json.js.map +1 -0
- package/dist/utils/index.d.ts +82 -18
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +387 -80
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/json-output.d.ts +10 -0
- package/dist/utils/json-output.d.ts.map +1 -1
- package/dist/utils/json-output.js +14 -0
- package/dist/utils/json-output.js.map +1 -1
- package/dist/utils/payload-store.d.ts +40 -0
- package/dist/utils/payload-store.d.ts.map +1 -0
- package/dist/utils/payload-store.js +82 -0
- package/dist/utils/payload-store.js.map +1 -0
- package/dist/utils/request-capture.d.ts +26 -0
- package/dist/utils/request-capture.d.ts.map +1 -1
- package/dist/utils/request-capture.js +67 -0
- package/dist/utils/request-capture.js.map +1 -1
- package/dist/utils/select-prompt.d.ts +32 -0
- package/dist/utils/select-prompt.d.ts.map +1 -0
- package/dist/utils/select-prompt.js +71 -0
- package/dist/utils/select-prompt.js.map +1 -0
- package/dist/utils/update-notifier.d.ts.map +1 -1
- package/dist/utils/update-notifier.js +30 -3
- package/dist/utils/update-notifier.js.map +1 -1
- package/dist/web/assets/{index-DSg2xrPw.js → index-n4de71HW.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +10 -6
- package/dist/commands/add-integration.d.ts +0 -20
- package/dist/commands/add-integration.d.ts.map +0 -1
- package/dist/commands/add-integration.js +0 -385
- package/dist/commands/add-integration.js.map +0 -1
- package/dist/commands/add-tool.d.ts +0 -17
- package/dist/commands/add-tool.d.ts.map +0 -1
- package/dist/commands/add-tool.js +0 -225
- package/dist/commands/add-tool.js.map +0 -1
- package/dist/commands/chat.d.ts +0 -20
- package/dist/commands/chat.d.ts.map +0 -1
- package/dist/commands/chat.js +0 -545
- package/dist/commands/chat.js.map +0 -1
- package/dist/commands/create-agent.d.ts +0 -17
- package/dist/commands/create-agent.d.ts.map +0 -1
- package/dist/commands/create-agent.js +0 -18
- package/dist/commands/create-agent.js.map +0 -1
- package/dist/commands/lsp.d.ts +0 -13
- package/dist/commands/lsp.d.ts.map +0 -1
- package/dist/commands/lsp.js +0 -17
- package/dist/commands/lsp.js.map +0 -1
- package/dist/commands/widget.d.ts +0 -18
- package/dist/commands/widget.d.ts.map +0 -1
- package/dist/commands/widget.js +0 -153
- package/dist/commands/widget.js.map +0 -1
- package/dist/lsp/cross-refs.d.ts +0 -53
- package/dist/lsp/cross-refs.d.ts.map +0 -1
- package/dist/lsp/cross-refs.js +0 -330
- package/dist/lsp/cross-refs.js.map +0 -1
- package/dist/lsp/cross-refs.test.d.ts +0 -2
- package/dist/lsp/cross-refs.test.d.ts.map +0 -1
- package/dist/lsp/cross-refs.test.js +0 -130
- package/dist/lsp/cross-refs.test.js.map +0 -1
- package/dist/lsp/schemas.d.ts +0 -57
- package/dist/lsp/schemas.d.ts.map +0 -1
- package/dist/lsp/schemas.js +0 -126
- package/dist/lsp/schemas.js.map +0 -1
- package/dist/lsp/schemas.test.d.ts +0 -2
- package/dist/lsp/schemas.test.d.ts.map +0 -1
- package/dist/lsp/schemas.test.js +0 -74
- package/dist/lsp/schemas.test.js.map +0 -1
- package/dist/lsp/server.d.ts +0 -11
- package/dist/lsp/server.d.ts.map +0 -1
- package/dist/lsp/server.js +0 -205
- package/dist/lsp/server.js.map +0 -1
- package/dist/ui/views/ChatView.d.ts +0 -26
- package/dist/ui/views/ChatView.d.ts.map +0 -1
- package/dist/ui/views/ChatView.js +0 -96
- package/dist/ui/views/ChatView.js.map +0 -1
- package/dist/ui/views/__tests__/StatusView.test.d.ts +0 -2
- package/dist/ui/views/__tests__/StatusView.test.d.ts.map +0 -1
- package/dist/ui/views/__tests__/StatusView.test.js +0 -158
- package/dist/ui/views/__tests__/StatusView.test.js.map +0 -1
package/dist/api-client/index.js
CHANGED
|
@@ -13,12 +13,13 @@
|
|
|
13
13
|
* - x-aui-environment: "staging" | "production"
|
|
14
14
|
* - x-aui-origin: "stores"
|
|
15
15
|
*/
|
|
16
|
-
import fetch from "
|
|
16
|
+
import fetch, { FetchTimeoutError } from "../utils/fetch-with-timeout.js";
|
|
17
17
|
import { randomUUID } from "crypto";
|
|
18
18
|
import * as fs from "fs";
|
|
19
19
|
import * as path from "path";
|
|
20
20
|
import { getAgentSettingsBaseUrl, getAgentSettingsWriteUrl, getAgentManagementBaseUrl, } from "../config/index.js";
|
|
21
|
-
import { captureRequest } from "../utils/request-capture.js";
|
|
21
|
+
import { captureRequest, formatAsCurlSafe } from "../utils/request-capture.js";
|
|
22
|
+
import { trace } from "@opentelemetry/api";
|
|
22
23
|
// ─── Helpers ───
|
|
23
24
|
function stripTestCurlResponse(settings) {
|
|
24
25
|
if (!settings || typeof settings !== "object")
|
|
@@ -72,6 +73,22 @@ export class AUIClient {
|
|
|
72
73
|
if (scope.organizationId)
|
|
73
74
|
this.organizationId = scope.organizationId;
|
|
74
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Describe the request a given call WOULD send — the resolved URL, the
|
|
78
|
+
* exact headers, the method, and the body — without issuing it. Lets a
|
|
79
|
+
* caller attach an `aui.curl_repro` / `http.url` to its command span
|
|
80
|
+
* (the secrets in the headers are redacted by `formatAsCurlSafe`) so the
|
|
81
|
+
* endpoint a command hit is visible in Logfire, mirroring the preflight /
|
|
82
|
+
* validate telemetry. The URL is built the same way `request()` does.
|
|
83
|
+
*/
|
|
84
|
+
previewRequest(method, endpoint, body) {
|
|
85
|
+
return {
|
|
86
|
+
method: method.toUpperCase(),
|
|
87
|
+
url: `${this.baseUrl}/${endpoint.replace(/^\/+/, "")}`,
|
|
88
|
+
headers: this.getHeaders(),
|
|
89
|
+
body,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
75
92
|
/**
|
|
76
93
|
* Build request headers matching the real AUI API expectations
|
|
77
94
|
*/
|
|
@@ -91,9 +108,20 @@ export class AUIClient {
|
|
|
91
108
|
}
|
|
92
109
|
/**
|
|
93
110
|
* Make an HTTP request to the AUI backend.
|
|
111
|
+
*
|
|
94
112
|
* On 401, automatically attempts a token refresh and retries once.
|
|
113
|
+
*
|
|
114
|
+
* The internal options object explicitly carries `_isRetry` (this is the
|
|
115
|
+
* second attempt after a 401-triggered refresh) and `_isRefreshCall` (this
|
|
116
|
+
* IS the refresh call itself, so a 401 here must NOT trigger another
|
|
117
|
+
* refresh — that would cause stack-overflow). Previously the refresh call
|
|
118
|
+
* was identified by URL substring match on `"token/refresh"`, which is
|
|
119
|
+
* fragile against future endpoint renames; the explicit flag removes that
|
|
120
|
+
* footgun.
|
|
95
121
|
*/
|
|
96
|
-
async request(method, endpoint, body,
|
|
122
|
+
async request(method, endpoint, body, internal) {
|
|
123
|
+
const _isRetry = internal?._isRetry === true;
|
|
124
|
+
const _isRefreshCall = internal?._isRefreshCall === true;
|
|
97
125
|
const url = `${this.baseUrl}/${endpoint.replace(/^\/+/, "")}`;
|
|
98
126
|
const options = {
|
|
99
127
|
method,
|
|
@@ -107,15 +135,18 @@ export class AUIClient {
|
|
|
107
135
|
response = await fetch(url, options);
|
|
108
136
|
}
|
|
109
137
|
catch (error) {
|
|
138
|
+
// FetchTimeoutError already has a clear, structured message. Other
|
|
139
|
+
// errors get wrapped to keep the legacy "Network error: ..." prefix
|
|
140
|
+
// so existing callers / log scrapers don't break.
|
|
141
|
+
if (error instanceof FetchTimeoutError)
|
|
142
|
+
throw error;
|
|
110
143
|
throw new Error(`Network error: ${error instanceof Error ? error.message : "Connection failed"}`);
|
|
111
144
|
}
|
|
112
145
|
if (!response.ok) {
|
|
113
|
-
if (response.status === 401 &&
|
|
114
|
-
!_isRetry &&
|
|
115
|
-
!endpoint.includes("token/refresh")) {
|
|
146
|
+
if (response.status === 401 && !_isRetry && !_isRefreshCall) {
|
|
116
147
|
const refreshed = await this.attemptTokenRefresh();
|
|
117
148
|
if (refreshed) {
|
|
118
|
-
return this.request(method, endpoint, body, true);
|
|
149
|
+
return this.request(method, endpoint, body, { _isRetry: true });
|
|
119
150
|
}
|
|
120
151
|
}
|
|
121
152
|
let errorBody;
|
|
@@ -254,8 +285,32 @@ export class AUIClient {
|
|
|
254
285
|
loginWithOtp: (email, otp) => this.post("identity/user/login", { email, password: otp, isOTP: true }),
|
|
255
286
|
/** Logout (invalidate session server-side) */
|
|
256
287
|
logout: () => this.post("identity/user/logout", {}),
|
|
257
|
-
/**
|
|
258
|
-
|
|
288
|
+
/**
|
|
289
|
+
* Exchange a refresh token for a new access + refresh token pair.
|
|
290
|
+
*
|
|
291
|
+
* Marked `_isRefreshCall: true` so the generic `request()` 401 handler
|
|
292
|
+
* NEVER tries to refresh-and-retry the refresh call itself (would recurse
|
|
293
|
+
* indefinitely on an expired refresh token). Internal refresh inside
|
|
294
|
+
* `_doTokenRefresh` bypasses `request()` entirely via direct `fetch`, but
|
|
295
|
+
* this public surface needs the same guarantee for external callers.
|
|
296
|
+
*/
|
|
297
|
+
refreshToken: (refreshToken) => this.request("POST", "identity/user/token/refresh", { refreshToken }, { _isRefreshCall: true }),
|
|
298
|
+
/**
|
|
299
|
+
* Switch the active organization and mint a NEW access token scoped to
|
|
300
|
+
* the target org. This is the only way to obtain a token whose
|
|
301
|
+
* `organizationId` claim matches an org other than the one the user
|
|
302
|
+
* logged in with — the org-id *header* alone is advisory and the
|
|
303
|
+
* gateway trusts the token claim (so org-scoped runtime endpoints like
|
|
304
|
+
* Apollo's create-task 404 with "Agent not found" when the two
|
|
305
|
+
* diverge).
|
|
306
|
+
*
|
|
307
|
+
* Mirrors the playground's org-switch call. Authenticated by the
|
|
308
|
+
* caller's CURRENT token (sent as `x-access-token` by `getHeaders`);
|
|
309
|
+
* the TARGET org goes in the body as `{ organization }`. The response
|
|
310
|
+
* shape is read defensively by callers because the field nesting
|
|
311
|
+
* (`data.token` vs `token`) is owned by the identity service.
|
|
312
|
+
*/
|
|
313
|
+
switchOrganization: (organizationId) => this.post("identity/user/switch-organization", { organization: organizationId }),
|
|
259
314
|
};
|
|
260
315
|
// ─── Organizations ───
|
|
261
316
|
/** List and fetch organizations the current user belongs to */
|
|
@@ -322,22 +377,95 @@ export class AUIClient {
|
|
|
322
377
|
});
|
|
323
378
|
if (filters?.network_id)
|
|
324
379
|
qs.set("network_id", filters.network_id);
|
|
380
|
+
if (filters?.scope_type)
|
|
381
|
+
qs.set("scope_type", filters.scope_type);
|
|
382
|
+
if (filters?.kind)
|
|
383
|
+
qs.set("kind", filters.kind);
|
|
384
|
+
if (filters?.network_category_id)
|
|
385
|
+
qs.set("network_category_id", filters.network_category_id);
|
|
386
|
+
if (filters?.account_id)
|
|
387
|
+
qs.set("account_id", filters.account_id);
|
|
388
|
+
if (filters?.name)
|
|
389
|
+
qs.set("name", filters.name);
|
|
325
390
|
const url = `${getAgentManagementBaseUrl()}/v1/agents?${qs}`;
|
|
326
|
-
|
|
391
|
+
// `list_agents` is the fallback resolver used by `aui pull` /
|
|
392
|
+
// `aui import-agent` when `.auirc` has no `agent_management_id`
|
|
393
|
+
// (legacy projects pre-PR #54). Full preflight telemetry —
|
|
394
|
+
// curl repro + truncated response — so Logfire shows which
|
|
395
|
+
// sibling agents the resolver saw and which one it picked.
|
|
396
|
+
// Mirrors the contract used by `get_agent` and `get_version`.
|
|
397
|
+
const { resp, responseText } = await this.preflightFetch({
|
|
398
|
+
name: "list_agents",
|
|
399
|
+
label: "list agents",
|
|
400
|
+
url,
|
|
327
401
|
method: "GET",
|
|
328
|
-
|
|
329
|
-
|
|
402
|
+
extraAttrs: {
|
|
403
|
+
organization_id: orgId,
|
|
404
|
+
page,
|
|
405
|
+
size,
|
|
406
|
+
...(filters?.network_id
|
|
407
|
+
? { filter_network_id: filters.network_id }
|
|
408
|
+
: {}),
|
|
409
|
+
...(filters?.network_category_id
|
|
410
|
+
? { filter_network_category_id: filters.network_category_id }
|
|
411
|
+
: {}),
|
|
412
|
+
...(filters?.scope_type ? { filter_scope_type: filters.scope_type } : {}),
|
|
413
|
+
...(filters?.kind ? { filter_kind: filters.kind } : {}),
|
|
414
|
+
...(filters?.account_id ? { filter_account_id: filters.account_id } : {}),
|
|
415
|
+
...(filters?.name ? { filter_name: filters.name } : {}),
|
|
416
|
+
},
|
|
417
|
+
extractAttrs: (parsed) => {
|
|
418
|
+
const out = {};
|
|
419
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
420
|
+
const obj = parsed;
|
|
421
|
+
if (Array.isArray(obj.items))
|
|
422
|
+
out.items_count = obj.items.length;
|
|
423
|
+
if (typeof obj.total === "number")
|
|
424
|
+
out.total = obj.total;
|
|
425
|
+
if (typeof obj.pages === "number")
|
|
426
|
+
out.pages = obj.pages;
|
|
427
|
+
// Capture the bundle_mode breakdown for the page — useful
|
|
428
|
+
// when the resolver picks the "wrong" sibling on a
|
|
429
|
+
// bundle/records mixed account.
|
|
430
|
+
if (Array.isArray(obj.items)) {
|
|
431
|
+
let bundleTrue = 0;
|
|
432
|
+
let bundleFalse = 0;
|
|
433
|
+
let bundleUnset = 0;
|
|
434
|
+
for (const it of obj.items) {
|
|
435
|
+
const bm = it && typeof it === "object"
|
|
436
|
+
? it.bundle_mode
|
|
437
|
+
: undefined;
|
|
438
|
+
if (bm === true)
|
|
439
|
+
bundleTrue++;
|
|
440
|
+
else if (bm === false)
|
|
441
|
+
bundleFalse++;
|
|
442
|
+
else
|
|
443
|
+
bundleUnset++;
|
|
444
|
+
}
|
|
445
|
+
out.items_bundle_mode_true = bundleTrue;
|
|
446
|
+
out.items_bundle_mode_false = bundleFalse;
|
|
447
|
+
out.items_bundle_mode_unset = bundleUnset;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return out;
|
|
451
|
+
},
|
|
452
|
+
});
|
|
330
453
|
if (!resp.ok) {
|
|
331
454
|
let errorBody;
|
|
332
455
|
try {
|
|
333
|
-
errorBody =
|
|
456
|
+
errorBody = JSON.parse(responseText);
|
|
334
457
|
}
|
|
335
458
|
catch {
|
|
336
|
-
errorBody =
|
|
459
|
+
errorBody = responseText || null;
|
|
337
460
|
}
|
|
338
461
|
throw new AUIAPIError(resp.status, `agents: ${resp.statusText}`, errorBody);
|
|
339
462
|
}
|
|
340
|
-
|
|
463
|
+
try {
|
|
464
|
+
return JSON.parse(responseText);
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
throw new AUIAPIError(resp.status, `agents: invalid JSON response`, responseText);
|
|
468
|
+
}
|
|
341
469
|
},
|
|
342
470
|
listVersions: async (agentId, page = 1, size = 50, filters) => {
|
|
343
471
|
const qs = new URLSearchParams({
|
|
@@ -387,22 +515,69 @@ export class AUIClient {
|
|
|
387
515
|
return (await resp.json());
|
|
388
516
|
},
|
|
389
517
|
getAgent: async (agentId) => {
|
|
518
|
+
// `GET /v1/agents/{id}` is the dispatch oracle for the whole CLI —
|
|
519
|
+
// every `aui import-agent`, `aui pull`, and `aui push` hits it
|
|
520
|
+
// before touching the per-entity / bundle endpoints (see
|
|
521
|
+
// `commands/util/agent-mode.ts → detectAgentBundleMode`). When
|
|
522
|
+
// a user reports "the CLI hit the wrong push endpoint" or
|
|
523
|
+
// "import keeps failing on this agent", the FIRST question is
|
|
524
|
+
// "what did the get-agent response say (especially bundle_mode)?".
|
|
525
|
+
//
|
|
526
|
+
// Telemetry is wired through `preflightFetch` (shared with
|
|
527
|
+
// `getVersion` and `listAgents`), which attaches a span event
|
|
528
|
+
// with curl repro, status, duration, and response body. Using
|
|
529
|
+
// `addEvent` (vs `setAttribute`) preserves multiple per-command
|
|
530
|
+
// calls: push's `lookupAgentManagementInfoForPush` +
|
|
531
|
+
// `resolveVersionDraft` can call `getAgent` more than once, and
|
|
532
|
+
// we want each call visible as its own log entry under the
|
|
533
|
+
// parent span.
|
|
390
534
|
const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}`;
|
|
391
|
-
const resp = await this.
|
|
535
|
+
const { resp, responseText } = await this.preflightFetch({
|
|
536
|
+
name: "get_agent",
|
|
537
|
+
label: "get agent",
|
|
538
|
+
url,
|
|
392
539
|
method: "GET",
|
|
393
|
-
|
|
394
|
-
|
|
540
|
+
extraAttrs: { agent_id: agentId },
|
|
541
|
+
extractAttrs: (parsed, spanAttrs) => {
|
|
542
|
+
const out = {};
|
|
543
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
544
|
+
const obj = parsed;
|
|
545
|
+
if (typeof obj.bundle_mode === "boolean") {
|
|
546
|
+
out.bundle_mode = obj.bundle_mode;
|
|
547
|
+
// Also pin the most-recent observed mode at the span level
|
|
548
|
+
// so it's filterable without expanding the event list.
|
|
549
|
+
// Multiple calls per command overwrite — the last call
|
|
550
|
+
// wins, which is the one whose decision actually drove
|
|
551
|
+
// the dispatch.
|
|
552
|
+
spanAttrs["agent.bundle_mode"] = obj.bundle_mode;
|
|
553
|
+
}
|
|
554
|
+
if (typeof obj.name === "string")
|
|
555
|
+
out.agent_name = obj.name;
|
|
556
|
+
if (typeof obj.active_version_id === "string") {
|
|
557
|
+
out.active_version_id = obj.active_version_id;
|
|
558
|
+
}
|
|
559
|
+
if (typeof obj.kind === "string")
|
|
560
|
+
out.agent_kind = obj.kind;
|
|
561
|
+
}
|
|
562
|
+
return out;
|
|
563
|
+
},
|
|
564
|
+
});
|
|
395
565
|
if (!resp.ok) {
|
|
396
566
|
let errorBody;
|
|
397
567
|
try {
|
|
398
|
-
errorBody =
|
|
568
|
+
errorBody = JSON.parse(responseText);
|
|
399
569
|
}
|
|
400
570
|
catch {
|
|
401
|
-
errorBody =
|
|
571
|
+
errorBody = responseText || null;
|
|
402
572
|
}
|
|
403
573
|
throw new AUIAPIError(resp.status, `get agent: ${resp.statusText}`, errorBody);
|
|
404
574
|
}
|
|
405
|
-
|
|
575
|
+
try {
|
|
576
|
+
return JSON.parse(responseText);
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
throw new AUIAPIError(resp.status, `get agent: invalid JSON response`, responseText);
|
|
580
|
+
}
|
|
406
581
|
},
|
|
407
582
|
activateVersion: async (agentId, versionId) => {
|
|
408
583
|
const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/active-version`;
|
|
@@ -443,22 +618,58 @@ export class AUIClient {
|
|
|
443
618
|
return (await resp.json());
|
|
444
619
|
},
|
|
445
620
|
getVersion: async (agentId, versionId) => {
|
|
621
|
+
// `GET /v1/agents/{id}/versions/{id}` is the second preflight read
|
|
622
|
+
// every push/pull issues — it resolves the draft target (and
|
|
623
|
+
// returns the version's `status`, `version_number`, etc.). When
|
|
624
|
+
// a user reports "push failed with 422 not-a-draft" or "pull
|
|
625
|
+
// returned 404", the diff between WHAT the CLI saw here and WHAT
|
|
626
|
+
// it then sent on /push or /pull is the smoking gun. Full
|
|
627
|
+
// preflight telemetry — curl repro + truncated response — keeps
|
|
628
|
+
// both pieces of evidence in one Logfire row.
|
|
446
629
|
const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/versions/${versionId}`;
|
|
447
|
-
const resp = await this.
|
|
630
|
+
const { resp, responseText } = await this.preflightFetch({
|
|
631
|
+
name: "get_version",
|
|
632
|
+
label: "get version",
|
|
633
|
+
url,
|
|
448
634
|
method: "GET",
|
|
449
|
-
|
|
450
|
-
|
|
635
|
+
extraAttrs: { agent_id: agentId, version_id: versionId },
|
|
636
|
+
extractAttrs: (parsed) => {
|
|
637
|
+
const out = {};
|
|
638
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
639
|
+
const obj = parsed;
|
|
640
|
+
if (typeof obj.status === "string")
|
|
641
|
+
out.status = obj.status;
|
|
642
|
+
if (typeof obj.version_number === "number") {
|
|
643
|
+
out.version_number = obj.version_number;
|
|
644
|
+
}
|
|
645
|
+
if (typeof obj.version_revision_number === "number") {
|
|
646
|
+
out.version_revision_number = obj.version_revision_number;
|
|
647
|
+
}
|
|
648
|
+
if (typeof obj.label === "string")
|
|
649
|
+
out.label = obj.label;
|
|
650
|
+
if (typeof obj.is_active === "boolean") {
|
|
651
|
+
out.is_active = obj.is_active;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return out;
|
|
655
|
+
},
|
|
656
|
+
});
|
|
451
657
|
if (!resp.ok) {
|
|
452
658
|
let errorBody;
|
|
453
659
|
try {
|
|
454
|
-
errorBody =
|
|
660
|
+
errorBody = JSON.parse(responseText);
|
|
455
661
|
}
|
|
456
662
|
catch {
|
|
457
|
-
errorBody =
|
|
663
|
+
errorBody = responseText || null;
|
|
458
664
|
}
|
|
459
665
|
throw new AUIAPIError(resp.status, `get version: ${resp.statusText}`, errorBody);
|
|
460
666
|
}
|
|
461
|
-
|
|
667
|
+
try {
|
|
668
|
+
return JSON.parse(responseText);
|
|
669
|
+
}
|
|
670
|
+
catch {
|
|
671
|
+
throw new AUIAPIError(resp.status, `get version: invalid JSON response`, responseText);
|
|
672
|
+
}
|
|
462
673
|
},
|
|
463
674
|
updateVersion: async (agentId, versionId, data) => {
|
|
464
675
|
const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/versions/${versionId}`;
|
|
@@ -626,6 +837,404 @@ export class AUIClient {
|
|
|
626
837
|
}
|
|
627
838
|
return resp.json();
|
|
628
839
|
},
|
|
840
|
+
/**
|
|
841
|
+
* POST /v1/agents/{agentId}/versions/{versionId}/push
|
|
842
|
+
*
|
|
843
|
+
* JSON-only upload (multipart was retired on 2026-05-20 — see
|
|
844
|
+
* Notion "Agent Settings Push/Pull" and the OpenAPI under
|
|
845
|
+
* `PushRequestSchema`).
|
|
846
|
+
*
|
|
847
|
+
* Server flow:
|
|
848
|
+
* 1. Validates the assembled `bundle` through the v1 transcoder
|
|
849
|
+
* BEFORE acquiring the per-version Redis lock — fail-fast 422
|
|
850
|
+
* with a structured `BundleValidationResult` and no GCS /
|
|
851
|
+
* Mongo writes.
|
|
852
|
+
* 2. Canonicalizes the bundle bytes (deterministic key order,
|
|
853
|
+
* no extra whitespace), hashes with SHA-256, and writes a
|
|
854
|
+
* single `bundle.json` blob to GCS under
|
|
855
|
+
* `agent_id={id}/version_id={id}/version_tag={tag}/bundle.json`.
|
|
856
|
+
* 3. Bumps `version_revision_number` by exactly 1 and returns the
|
|
857
|
+
* new `version_tag`, `gcs_key`, `sha256`, and `size` so
|
|
858
|
+
* clients can verify integrity locally.
|
|
859
|
+
*
|
|
860
|
+
* Request body (`PushRequestSchema`):
|
|
861
|
+
* - `caller` : "agent_builder" | "ui" | "cli" (required)
|
|
862
|
+
* - `commit_message` : optional save note (≤ 4000 chars after trim)
|
|
863
|
+
* - `bundle` : `AgentSettingsBundle` — the CLI assembles
|
|
864
|
+
* this from local `.aui.json` files (see
|
|
865
|
+
* `assembleBundleFromFiles` in `utils/`).
|
|
866
|
+
*
|
|
867
|
+
* Auth: Bearer JWT + `X-Organization-ID` (same as other v1 routes).
|
|
868
|
+
* The legacy `x-api-key` fallback is NOT honoured here — Bearer only.
|
|
869
|
+
* Push requires `update` on `agent_settings` for the target agent.
|
|
870
|
+
*/
|
|
871
|
+
pushVersionBlobs: async (agentId, versionId, body) => {
|
|
872
|
+
const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/versions/${versionId}/push`;
|
|
873
|
+
// Defensive normalization of the request body:
|
|
874
|
+
// - `caller` always set (TS already enforces it, but be belt+suspenders)
|
|
875
|
+
// - `commit_message` trimmed + capped (server caps too, but the
|
|
876
|
+
// CLI shouldn't make the server do work on bad input)
|
|
877
|
+
// - `pushed_by` forwarded as-is when the caller supplies it
|
|
878
|
+
// (added 2026-05-25 per backend team — recorded on the
|
|
879
|
+
// blob revision row so revision provenance ties back to a
|
|
880
|
+
// real user, not just a surface). Only emitted when set so
|
|
881
|
+
// older server versions that don't read the field still
|
|
882
|
+
// accept the request body unchanged.
|
|
883
|
+
// - `bundle.schema_version` defaults to 1 if the caller forgot
|
|
884
|
+
const reqBody = {
|
|
885
|
+
caller: body.caller,
|
|
886
|
+
...(body.commit_message && body.commit_message.trim()
|
|
887
|
+
? { commit_message: body.commit_message.trim().slice(0, 4000) }
|
|
888
|
+
: {}),
|
|
889
|
+
...(body.pushed_by ? { pushed_by: body.pushed_by } : {}),
|
|
890
|
+
bundle: {
|
|
891
|
+
schema_version: body.bundle.schema_version ?? 1,
|
|
892
|
+
...body.bundle,
|
|
893
|
+
},
|
|
894
|
+
};
|
|
895
|
+
if (process.env.AUI_DEBUG) {
|
|
896
|
+
const b = reqBody.bundle;
|
|
897
|
+
const summary = {
|
|
898
|
+
schema_version: b.schema_version,
|
|
899
|
+
general_settings: b.general_settings ? "<object>" : null,
|
|
900
|
+
parameters: b.parameters ? b.parameters.length : null,
|
|
901
|
+
entities: b.entities ? b.entities.length : null,
|
|
902
|
+
integrations: b.integrations ? b.integrations.length : null,
|
|
903
|
+
rules: b.rules ? b.rules.length : null,
|
|
904
|
+
tools: b.tools ? b.tools.length : null,
|
|
905
|
+
};
|
|
906
|
+
console.log(`[debug] POST ${url} as caller=${reqBody.caller} pushed_by=${reqBody.pushed_by ?? "(absent)"}; bundle:`, JSON.stringify(summary));
|
|
907
|
+
}
|
|
908
|
+
const headers = {
|
|
909
|
+
Accept: "application/json",
|
|
910
|
+
"Content-Type": "application/json",
|
|
911
|
+
Authorization: `Bearer ${this.authToken}`,
|
|
912
|
+
"X-Organization-ID": this.organizationId,
|
|
913
|
+
};
|
|
914
|
+
// Bundle uploads can be moderately large (every parameter +
|
|
915
|
+
// entity + tool encoded as JSON). Cap the request at 5 minutes —
|
|
916
|
+
// long enough for a slow E2B sandbox cold-start, short enough
|
|
917
|
+
// that a truly hung request surfaces before the BFF's own
|
|
918
|
+
// pipeline timeout (BFF caps `aui push` at 5 min in
|
|
919
|
+
// `agent-builder-bff`'s `sandbox-command-client.ts:auiPush`).
|
|
920
|
+
// Override via `AUI_PUSH_TIMEOUT_MS` env var if needed (legacy
|
|
921
|
+
// `AUI_SNAPSHOT_TIMEOUT_MS` honoured too for back-compat).
|
|
922
|
+
const pushTimeoutMs = (() => {
|
|
923
|
+
const fromEnv = process.env.AUI_PUSH_TIMEOUT_MS ?? process.env.AUI_SNAPSHOT_TIMEOUT_MS;
|
|
924
|
+
if (fromEnv) {
|
|
925
|
+
const parsed = parseInt(fromEnv, 10);
|
|
926
|
+
if (!Number.isNaN(parsed) && parsed >= 0)
|
|
927
|
+
return parsed;
|
|
928
|
+
}
|
|
929
|
+
return 300_000;
|
|
930
|
+
})();
|
|
931
|
+
const pushController = new AbortController();
|
|
932
|
+
const pushTimer = pushTimeoutMs > 0
|
|
933
|
+
? setTimeout(() => pushController.abort(), pushTimeoutMs)
|
|
934
|
+
: null;
|
|
935
|
+
const pushStart = Date.now();
|
|
936
|
+
const serialized = JSON.stringify(reqBody);
|
|
937
|
+
let resp;
|
|
938
|
+
try {
|
|
939
|
+
resp = await globalThis.fetch(url, {
|
|
940
|
+
method: "POST",
|
|
941
|
+
headers,
|
|
942
|
+
body: serialized,
|
|
943
|
+
signal: pushController.signal,
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
catch (err) {
|
|
947
|
+
if (pushTimer)
|
|
948
|
+
clearTimeout(pushTimer);
|
|
949
|
+
const elapsedMs = Date.now() - pushStart;
|
|
950
|
+
const isAbort = err instanceof Error &&
|
|
951
|
+
(err.name === "AbortError" || pushController.signal.aborted);
|
|
952
|
+
if (isAbort && pushTimeoutMs > 0 && elapsedMs >= pushTimeoutMs - 50) {
|
|
953
|
+
const msg = `Push blob upload timed out after ${elapsedMs}ms (limit ${pushTimeoutMs}ms): POST ${url}. ` +
|
|
954
|
+
`Override with AUI_PUSH_TIMEOUT_MS=<ms> if your agent is unusually large.`;
|
|
955
|
+
captureRequest({
|
|
956
|
+
timestamp: new Date().toISOString(),
|
|
957
|
+
method: "POST",
|
|
958
|
+
url,
|
|
959
|
+
headers,
|
|
960
|
+
body: {
|
|
961
|
+
caller: reqBody.caller,
|
|
962
|
+
commit_message: reqBody.commit_message,
|
|
963
|
+
pushed_by: reqBody.pushed_by,
|
|
964
|
+
bundle_size_bytes: serialized.length,
|
|
965
|
+
},
|
|
966
|
+
status: 0,
|
|
967
|
+
responseBody: msg,
|
|
968
|
+
label: "push version blobs",
|
|
969
|
+
success: false,
|
|
970
|
+
});
|
|
971
|
+
return { success: false, error: msg };
|
|
972
|
+
}
|
|
973
|
+
throw err;
|
|
974
|
+
}
|
|
975
|
+
if (pushTimer)
|
|
976
|
+
clearTimeout(pushTimer);
|
|
977
|
+
// Read the body ONCE — used for telemetry, the error envelope,
|
|
978
|
+
// and the success parse. (Web fetch bodies are single-shot.)
|
|
979
|
+
const responseText = await resp.text();
|
|
980
|
+
// Summary body for both captureRequest and the span curl. We do
|
|
981
|
+
// NOT inline the full bundle (it can be megabytes) — the
|
|
982
|
+
// reproducible signal for a push is the endpoint + the RESPONSE
|
|
983
|
+
// (new_version_tag on success, or the 422 BundleValidationResult
|
|
984
|
+
// on failure), both of which we capture in full (truncated 32KB).
|
|
985
|
+
const pushSummaryBody = {
|
|
986
|
+
caller: reqBody.caller,
|
|
987
|
+
commit_message: reqBody.commit_message,
|
|
988
|
+
pushed_by: reqBody.pushed_by,
|
|
989
|
+
bundle_size_bytes: serialized.length,
|
|
990
|
+
};
|
|
991
|
+
captureRequest({
|
|
992
|
+
timestamp: new Date().toISOString(),
|
|
993
|
+
method: "POST",
|
|
994
|
+
url,
|
|
995
|
+
headers,
|
|
996
|
+
body: pushSummaryBody,
|
|
997
|
+
status: resp.status,
|
|
998
|
+
responseBody: responseText,
|
|
999
|
+
label: "push version blobs",
|
|
1000
|
+
success: resp.ok,
|
|
1001
|
+
});
|
|
1002
|
+
// Attach the replayable curl + response to the active span
|
|
1003
|
+
// (`aui.push.task.bundle`) for BOTH success and failure, so the
|
|
1004
|
+
// bundle-mode push endpoint is fully visible in Logfire — the
|
|
1005
|
+
// counterpart to the records-mode per-entity write curls.
|
|
1006
|
+
this.attachHttpCurlTelemetry({
|
|
1007
|
+
name: "push_bundle",
|
|
1008
|
+
label: "push version blobs",
|
|
1009
|
+
method: "POST",
|
|
1010
|
+
url,
|
|
1011
|
+
headers,
|
|
1012
|
+
requestBody: pushSummaryBody,
|
|
1013
|
+
responseText,
|
|
1014
|
+
status: resp.status,
|
|
1015
|
+
success: resp.ok,
|
|
1016
|
+
durationMs: Date.now() - pushStart,
|
|
1017
|
+
});
|
|
1018
|
+
if (!resp.ok) {
|
|
1019
|
+
let errorBody;
|
|
1020
|
+
try {
|
|
1021
|
+
errorBody = JSON.parse(responseText);
|
|
1022
|
+
}
|
|
1023
|
+
catch {
|
|
1024
|
+
errorBody = responseText;
|
|
1025
|
+
}
|
|
1026
|
+
if (process.env.AUI_DEBUG) {
|
|
1027
|
+
console.log(`[debug] push version blobs failed: ${resp.status} ${JSON.stringify(errorBody)}`);
|
|
1028
|
+
}
|
|
1029
|
+
// 422 has two flavours per the doc:
|
|
1030
|
+
// - request validation: { detail: "string" } or { detail: [{ loc, msg, type }] }
|
|
1031
|
+
// - bundle validation : BundleValidationResult JSON
|
|
1032
|
+
// Surface whichever is present without losing the structured body.
|
|
1033
|
+
const detail = errorBody?.detail;
|
|
1034
|
+
let errorMsg;
|
|
1035
|
+
if (Array.isArray(detail)) {
|
|
1036
|
+
errorMsg = detail
|
|
1037
|
+
.map((d) => `${d.msg ?? d.message ?? ""} (${(d.loc ?? []).join(".")})`)
|
|
1038
|
+
.join("; ");
|
|
1039
|
+
}
|
|
1040
|
+
else if (typeof detail === "string") {
|
|
1041
|
+
errorMsg = detail;
|
|
1042
|
+
}
|
|
1043
|
+
else if (typeof errorBody === "string") {
|
|
1044
|
+
errorMsg = errorBody;
|
|
1045
|
+
}
|
|
1046
|
+
else {
|
|
1047
|
+
errorMsg = JSON.stringify(errorBody);
|
|
1048
|
+
}
|
|
1049
|
+
return {
|
|
1050
|
+
success: false,
|
|
1051
|
+
error: `${resp.status}: ${errorMsg}`,
|
|
1052
|
+
data: errorBody,
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
let responseData;
|
|
1056
|
+
try {
|
|
1057
|
+
responseData = (responseText ? JSON.parse(responseText) : undefined);
|
|
1058
|
+
}
|
|
1059
|
+
catch {
|
|
1060
|
+
responseData = undefined;
|
|
1061
|
+
}
|
|
1062
|
+
if (process.env.AUI_DEBUG) {
|
|
1063
|
+
console.log(`[debug] push version blobs succeeded: ${JSON.stringify(responseData)}`);
|
|
1064
|
+
}
|
|
1065
|
+
return { success: true, data: responseData };
|
|
1066
|
+
},
|
|
1067
|
+
/**
|
|
1068
|
+
* GET /v1/agents/{agentId}/versions/{versionId}/pull
|
|
1069
|
+
*
|
|
1070
|
+
* JSON-only read (signed-URL + `return_type=json` modes were
|
|
1071
|
+
* retired on 2026-05-20 — the new server returns the assembled
|
|
1072
|
+
* bundle inline as `PullReadResponse`).
|
|
1073
|
+
*
|
|
1074
|
+
* Caching: every `version_tag` is an immutable snapshot — Pull
|
|
1075
|
+
* tries Redis first. `expires_at` on the response is the snapshot
|
|
1076
|
+
* row's TTL, not a per-request expiry.
|
|
1077
|
+
*
|
|
1078
|
+
* Auth: Bearer JWT + `X-Organization-ID`. The legacy `x-api-key`
|
|
1079
|
+
* fallback is NOT honoured here — Bearer only. Pull requires
|
|
1080
|
+
* `read` on `agent_settings`.
|
|
1081
|
+
*/
|
|
1082
|
+
pullVersionBlobs: async (agentId, versionId, options = {}) => {
|
|
1083
|
+
const qs = new URLSearchParams();
|
|
1084
|
+
if (options.versionTag)
|
|
1085
|
+
qs.set("version_tag", options.versionTag);
|
|
1086
|
+
const search = qs.toString();
|
|
1087
|
+
const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/versions/${versionId}/pull${search ? `?${search}` : ""}`;
|
|
1088
|
+
if (process.env.AUI_DEBUG) {
|
|
1089
|
+
console.log(`[debug] GET ${url}`);
|
|
1090
|
+
}
|
|
1091
|
+
const pullHeaders = {
|
|
1092
|
+
Accept: "application/json",
|
|
1093
|
+
Authorization: `Bearer ${this.authToken}`,
|
|
1094
|
+
"X-Organization-ID": this.organizationId,
|
|
1095
|
+
};
|
|
1096
|
+
const pullStart = Date.now();
|
|
1097
|
+
const resp = await this.agentManagementFetch(url, { method: "GET", headers: pullHeaders }, "pull version blobs");
|
|
1098
|
+
// Read the body ONCE — used for telemetry, the error envelope,
|
|
1099
|
+
// and the success parse.
|
|
1100
|
+
const responseText = await resp.text();
|
|
1101
|
+
// Attach the replayable curl + response to the active span
|
|
1102
|
+
// (`aui.pull` / `aui.import`) for the bundle-mode `/pull` read —
|
|
1103
|
+
// the counterpart to the records-mode `/view` read curls, so the
|
|
1104
|
+
// import endpoint is fully visible in Logfire for both modes.
|
|
1105
|
+
this.attachHttpCurlTelemetry({
|
|
1106
|
+
name: "pull_bundle",
|
|
1107
|
+
label: "pull version blobs",
|
|
1108
|
+
method: "GET",
|
|
1109
|
+
url,
|
|
1110
|
+
headers: pullHeaders,
|
|
1111
|
+
responseText,
|
|
1112
|
+
status: resp.status,
|
|
1113
|
+
success: resp.ok,
|
|
1114
|
+
durationMs: Date.now() - pullStart,
|
|
1115
|
+
});
|
|
1116
|
+
if (!resp.ok) {
|
|
1117
|
+
let errorBody;
|
|
1118
|
+
try {
|
|
1119
|
+
errorBody = JSON.parse(responseText);
|
|
1120
|
+
}
|
|
1121
|
+
catch {
|
|
1122
|
+
errorBody = responseText;
|
|
1123
|
+
}
|
|
1124
|
+
throw new AUIAPIError(resp.status, `pull version blobs: ${resp.statusText}`, errorBody);
|
|
1125
|
+
}
|
|
1126
|
+
return JSON.parse(responseText);
|
|
1127
|
+
},
|
|
1128
|
+
/**
|
|
1129
|
+
* @deprecated Multipart-files upload was removed on 2026-05-20. The
|
|
1130
|
+
* new server only accepts the JSON `PushBundleRequest` shape via
|
|
1131
|
+
* {@link pushVersionBlobs}. Callers must assemble the bundle from
|
|
1132
|
+
* local files (see `utils/assembleBundleFromFiles`) and pass it
|
|
1133
|
+
* in directly.
|
|
1134
|
+
*/
|
|
1135
|
+
pushSnapshot: async () => {
|
|
1136
|
+
throw new AUIAPIError(410, "pushSnapshot is gone. The multipart /push endpoint was retired on 2026-05-20; use pushVersionBlobs with an assembled AgentSettingsBundle.");
|
|
1137
|
+
},
|
|
1138
|
+
/**
|
|
1139
|
+
* @deprecated Signed-URL pull mode was removed on 2026-05-20. The
|
|
1140
|
+
* new server returns the assembled bundle inline as
|
|
1141
|
+
* {@link PullReadResponse}; consumers should call
|
|
1142
|
+
* {@link pullVersionBlobs} directly and read `response.bundle`
|
|
1143
|
+
* instead of paging through per-file signed URLs.
|
|
1144
|
+
*/
|
|
1145
|
+
getSnapshot: async () => {
|
|
1146
|
+
throw new AUIAPIError(410, "getSnapshot is gone. Signed-URL pull mode was retired on 2026-05-20; use pullVersionBlobs and read response.bundle.");
|
|
1147
|
+
},
|
|
1148
|
+
/**
|
|
1149
|
+
* POST /v1/agents/{agentId}/validate
|
|
1150
|
+
*
|
|
1151
|
+
* Server-side validation for an agent bundle. The CLI assembles every
|
|
1152
|
+
* `.aui.json` file into a flat payload and forwards it as-is — the
|
|
1153
|
+
* endpoint runs the canonical pydantic + cross-reference checks that
|
|
1154
|
+
* used to be duplicated in the CLI (AJV + JS, see commented-out
|
|
1155
|
+
* `_validate` in commands/validate.tsx).
|
|
1156
|
+
*
|
|
1157
|
+
* The endpoint always returns 200 on success even when `valid: false`
|
|
1158
|
+
* (the body carries per-section errors/warnings). A non-2xx response
|
|
1159
|
+
* means the request itself was malformed (422) or the backend is down
|
|
1160
|
+
* (5xx) and we surface it as an AUIAPIError so the caller can attach a
|
|
1161
|
+
* "validation infra failure" message to its span instead of pretending
|
|
1162
|
+
* the agent is valid.
|
|
1163
|
+
*/
|
|
1164
|
+
validateAgent: async (agentId, payload) => {
|
|
1165
|
+
// Validate is the only gate between local files and a partial-state
|
|
1166
|
+
// push, so its span needs to be self-contained for triage: full curl
|
|
1167
|
+
// repro (redacted), HTTP method/url/status, and the response body —
|
|
1168
|
+
// on every call, not just failures. The push-task spans already do
|
|
1169
|
+
// this; we mirror the contract here so any "why did validate say
|
|
1170
|
+
// valid:false?" question can be answered straight from Logfire
|
|
1171
|
+
// without re-running the CLI.
|
|
1172
|
+
const url = `${getAgentManagementBaseUrl()}/v1/agents/${agentId}/validate`;
|
|
1173
|
+
const method = "POST";
|
|
1174
|
+
const headers = this.agentManagementWriteHeaders();
|
|
1175
|
+
const bodyStr = JSON.stringify(payload);
|
|
1176
|
+
const startedAt = Date.now();
|
|
1177
|
+
let resp;
|
|
1178
|
+
try {
|
|
1179
|
+
resp = await this.agentManagementFetch(url, { method, headers, body: bodyStr }, "validate agent");
|
|
1180
|
+
}
|
|
1181
|
+
catch (err) {
|
|
1182
|
+
// Network-level failure (ECONNREFUSED, DNS, abort, ...). The
|
|
1183
|
+
// upstream catch in `_validate` already records status / message;
|
|
1184
|
+
// we attach the request-side telemetry (curl + payload size +
|
|
1185
|
+
// duration) before re-throwing so the trace tells the full story.
|
|
1186
|
+
const networkErr = err instanceof Error ? err : new Error(String(err));
|
|
1187
|
+
this.attachValidateTelemetry({
|
|
1188
|
+
method,
|
|
1189
|
+
url,
|
|
1190
|
+
headers,
|
|
1191
|
+
payload,
|
|
1192
|
+
requestBodyStr: bodyStr,
|
|
1193
|
+
responseText: networkErr.message,
|
|
1194
|
+
status: 0,
|
|
1195
|
+
success: false,
|
|
1196
|
+
durationMs: Date.now() - startedAt,
|
|
1197
|
+
});
|
|
1198
|
+
throw networkErr;
|
|
1199
|
+
}
|
|
1200
|
+
// Read once — Response bodies can only be consumed a single time
|
|
1201
|
+
// and we need the same string for telemetry, error envelopes, and
|
|
1202
|
+
// the success JSON parse below.
|
|
1203
|
+
let responseText;
|
|
1204
|
+
try {
|
|
1205
|
+
responseText = await resp.text();
|
|
1206
|
+
}
|
|
1207
|
+
catch (e) {
|
|
1208
|
+
responseText = "";
|
|
1209
|
+
}
|
|
1210
|
+
this.attachValidateTelemetry({
|
|
1211
|
+
method,
|
|
1212
|
+
url,
|
|
1213
|
+
headers,
|
|
1214
|
+
payload,
|
|
1215
|
+
requestBodyStr: bodyStr,
|
|
1216
|
+
responseText,
|
|
1217
|
+
status: resp.status,
|
|
1218
|
+
success: resp.ok,
|
|
1219
|
+
durationMs: Date.now() - startedAt,
|
|
1220
|
+
});
|
|
1221
|
+
if (!resp.ok) {
|
|
1222
|
+
let errorBody;
|
|
1223
|
+
try {
|
|
1224
|
+
errorBody = JSON.parse(responseText);
|
|
1225
|
+
}
|
|
1226
|
+
catch {
|
|
1227
|
+
errorBody = responseText || null;
|
|
1228
|
+
}
|
|
1229
|
+
throw new AUIAPIError(resp.status, `validate agent: ${resp.statusText}`, errorBody);
|
|
1230
|
+
}
|
|
1231
|
+
try {
|
|
1232
|
+
return JSON.parse(responseText);
|
|
1233
|
+
}
|
|
1234
|
+
catch {
|
|
1235
|
+
throw new AUIAPIError(resp.status, `validate agent: invalid JSON response`, responseText);
|
|
1236
|
+
}
|
|
1237
|
+
},
|
|
629
1238
|
};
|
|
630
1239
|
async agentManagementFetch(url, init, label, _isRetry = false) {
|
|
631
1240
|
const resp = await fetch(url, init);
|
|
@@ -674,6 +1283,268 @@ export class AUIClient {
|
|
|
674
1283
|
});
|
|
675
1284
|
return resp;
|
|
676
1285
|
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Attach validate-call telemetry to the currently-active span. Called on
|
|
1288
|
+
* every validate request — success, validation-fail, and infra-fail —
|
|
1289
|
+
* so Logfire shows the full story (curl repro, request size, response
|
|
1290
|
+
* body, duration) without us having to re-run the CLI. Mirrors the
|
|
1291
|
+
* shape the `writeV2` failure-path telemetry uses for push tasks, with
|
|
1292
|
+
* the one difference that we attach on success too: validate is the
|
|
1293
|
+
* push safety gate, so even a `valid: true` response deserves a curl
|
|
1294
|
+
* in case the verdict was wrong (false negative) and someone needs to
|
|
1295
|
+
* audit later.
|
|
1296
|
+
*
|
|
1297
|
+
* Always wrapped in a try/catch — telemetry must never break a call.
|
|
1298
|
+
*/
|
|
1299
|
+
attachValidateTelemetry(input) {
|
|
1300
|
+
try {
|
|
1301
|
+
const span = trace.getActiveSpan();
|
|
1302
|
+
if (!span)
|
|
1303
|
+
return;
|
|
1304
|
+
const MAX = 32 * 1024;
|
|
1305
|
+
const truncatedResponse = input.responseText.length > MAX
|
|
1306
|
+
? input.responseText.slice(0, MAX) +
|
|
1307
|
+
`... [truncated ${input.responseText.length - MAX} bytes]`
|
|
1308
|
+
: input.responseText;
|
|
1309
|
+
span.setAttribute("http.method", input.method);
|
|
1310
|
+
span.setAttribute("http.url", input.url);
|
|
1311
|
+
// status === 0 ⇒ no HTTP response (network failure). We deliberately
|
|
1312
|
+
// *omit* http.status_code in that case so dashboards filtering on
|
|
1313
|
+
// `status_code = X` don't conflate "couldn't reach backend" with
|
|
1314
|
+
// some real status.
|
|
1315
|
+
if (input.status > 0) {
|
|
1316
|
+
span.setAttribute("http.status_code", input.status);
|
|
1317
|
+
}
|
|
1318
|
+
span.setAttribute("validate.remote.request_payload_bytes", Buffer.byteLength(input.requestBodyStr, "utf8"));
|
|
1319
|
+
span.setAttribute("validate.remote.response_body", truncatedResponse);
|
|
1320
|
+
span.setAttribute("validate.remote.duration_ms", input.durationMs);
|
|
1321
|
+
span.setAttribute("aui.curl_repro", formatAsCurlSafe({
|
|
1322
|
+
timestamp: new Date().toISOString(),
|
|
1323
|
+
method: input.method,
|
|
1324
|
+
url: input.url,
|
|
1325
|
+
headers: input.headers,
|
|
1326
|
+
body: input.payload,
|
|
1327
|
+
status: input.status,
|
|
1328
|
+
responseBody: input.responseText,
|
|
1329
|
+
label: "validate agent",
|
|
1330
|
+
success: input.success,
|
|
1331
|
+
}, { maxBodyBytes: MAX }));
|
|
1332
|
+
}
|
|
1333
|
+
catch {
|
|
1334
|
+
// Telemetry must never break the call path.
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Attach preflight HTTP telemetry to the currently-active span
|
|
1339
|
+
* (typically `aui.import` / `aui.pull` / `aui.push`).
|
|
1340
|
+
*
|
|
1341
|
+
* The CLI fires up to three preflight reads BEFORE issuing the
|
|
1342
|
+
* actual push/pull body:
|
|
1343
|
+
* 1. `GET /v1/agents/{id}` → reads `bundle_mode`
|
|
1344
|
+
* 2. `GET /v1/agents/{id}/versions/{id}` → resolves the draft/version
|
|
1345
|
+
* 3. `GET /v1/agents?network_id=…` → legacy-project fallback
|
|
1346
|
+
* when `.auirc` has no `agent_management_id`.
|
|
1347
|
+
*
|
|
1348
|
+
* All three are essential for triaging "push hit the wrong endpoint"
|
|
1349
|
+
* / "pull pulled the wrong version" reports. Each one is emitted as
|
|
1350
|
+
* its own span event with the full curl repro (auth redacted),
|
|
1351
|
+
* status, duration, response body (truncated), and a handful of
|
|
1352
|
+
* extracted highlights pulled by `extractAttrs` (e.g. `bundle_mode`,
|
|
1353
|
+
* `active_version_id`, version `status`, item counts).
|
|
1354
|
+
*
|
|
1355
|
+
* Best-effort throughout: telemetry must NEVER break the call path.
|
|
1356
|
+
*/
|
|
1357
|
+
attachPreflightTelemetry(input) {
|
|
1358
|
+
try {
|
|
1359
|
+
const span = trace.getActiveSpan();
|
|
1360
|
+
if (!span)
|
|
1361
|
+
return;
|
|
1362
|
+
const MAX = 32 * 1024;
|
|
1363
|
+
const truncatedResponse = input.responseText.length > MAX
|
|
1364
|
+
? input.responseText.slice(0, MAX) +
|
|
1365
|
+
`... [truncated ${input.responseText.length - MAX} bytes]`
|
|
1366
|
+
: input.responseText;
|
|
1367
|
+
const eventAttrs = {
|
|
1368
|
+
"http.method": input.method,
|
|
1369
|
+
"http.url": input.url,
|
|
1370
|
+
[`${input.name}.duration_ms`]: input.durationMs,
|
|
1371
|
+
[`${input.name}.success`]: input.success,
|
|
1372
|
+
[`${input.name}.response_body`]: truncatedResponse,
|
|
1373
|
+
"aui.curl_repro": formatAsCurlSafe({
|
|
1374
|
+
timestamp: new Date().toISOString(),
|
|
1375
|
+
method: input.method,
|
|
1376
|
+
url: input.url,
|
|
1377
|
+
headers: input.headers,
|
|
1378
|
+
body: input.requestBody,
|
|
1379
|
+
status: input.status,
|
|
1380
|
+
responseBody: input.responseText,
|
|
1381
|
+
label: input.label,
|
|
1382
|
+
success: input.success,
|
|
1383
|
+
}, { maxBodyBytes: MAX }),
|
|
1384
|
+
};
|
|
1385
|
+
// status === 0 ⇒ no HTTP response (network failure). Mirror the
|
|
1386
|
+
// validate-call convention and omit `http.status_code` so
|
|
1387
|
+
// dashboards filtering on status don't conflate "couldn't reach
|
|
1388
|
+
// backend" with a real status.
|
|
1389
|
+
if (input.status > 0) {
|
|
1390
|
+
eventAttrs["http.status_code"] = input.status;
|
|
1391
|
+
}
|
|
1392
|
+
if (input.extraAttrs) {
|
|
1393
|
+
for (const [k, v] of Object.entries(input.extraAttrs)) {
|
|
1394
|
+
eventAttrs[`${input.name}.${k}`] = v;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
// Parse the response body once for the per-endpoint highlight
|
|
1398
|
+
// extractor. The extractor can also push attributes onto the
|
|
1399
|
+
// parent span (`spanAttrs`) for things that are stable across
|
|
1400
|
+
// every call (e.g. `agent.bundle_mode`).
|
|
1401
|
+
if (input.extractAttrs) {
|
|
1402
|
+
const spanAttrs = {};
|
|
1403
|
+
try {
|
|
1404
|
+
const parsed = JSON.parse(input.responseText);
|
|
1405
|
+
const extracted = input.extractAttrs(parsed, spanAttrs) || {};
|
|
1406
|
+
for (const [k, v] of Object.entries(extracted)) {
|
|
1407
|
+
eventAttrs[`${input.name}.${k}`] = v;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
catch {
|
|
1411
|
+
// Response wasn't JSON (network failure stringified error,
|
|
1412
|
+
// 5xx HTML page, etc.) — fall through without highlights.
|
|
1413
|
+
}
|
|
1414
|
+
for (const [k, v] of Object.entries(spanAttrs)) {
|
|
1415
|
+
span.setAttribute(k, v);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
span.addEvent(input.name, eventAttrs);
|
|
1419
|
+
}
|
|
1420
|
+
catch {
|
|
1421
|
+
// Telemetry must never break the call path.
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Run a preflight `agentManagementFetch` and attach full
|
|
1426
|
+
* curl+request+response telemetry to the active span. Use for
|
|
1427
|
+
* `GET` reads that drive the push/pull dispatch (getAgent,
|
|
1428
|
+
* getVersion, listAgents). Returns the response text so callers
|
|
1429
|
+
* still parse the body themselves — keeps the existing single-
|
|
1430
|
+
* read-once invariant from `getAgent` (response bodies are
|
|
1431
|
+
* single-shot).
|
|
1432
|
+
*/
|
|
1433
|
+
async preflightFetch(input) {
|
|
1434
|
+
const headers = this.agentManagementHeaders();
|
|
1435
|
+
const startedAt = Date.now();
|
|
1436
|
+
let resp;
|
|
1437
|
+
try {
|
|
1438
|
+
resp = await this.agentManagementFetch(input.url, { method: input.method, headers }, input.label);
|
|
1439
|
+
}
|
|
1440
|
+
catch (err) {
|
|
1441
|
+
const networkErr = err instanceof Error ? err : new Error(String(err));
|
|
1442
|
+
this.attachPreflightTelemetry({
|
|
1443
|
+
name: input.name,
|
|
1444
|
+
label: input.label,
|
|
1445
|
+
method: input.method,
|
|
1446
|
+
url: input.url,
|
|
1447
|
+
headers,
|
|
1448
|
+
responseText: networkErr.message,
|
|
1449
|
+
status: 0,
|
|
1450
|
+
success: false,
|
|
1451
|
+
durationMs: Date.now() - startedAt,
|
|
1452
|
+
extraAttrs: input.extraAttrs,
|
|
1453
|
+
extractAttrs: input.extractAttrs,
|
|
1454
|
+
});
|
|
1455
|
+
throw networkErr;
|
|
1456
|
+
}
|
|
1457
|
+
let responseText;
|
|
1458
|
+
try {
|
|
1459
|
+
responseText = await resp.text();
|
|
1460
|
+
}
|
|
1461
|
+
catch {
|
|
1462
|
+
responseText = "";
|
|
1463
|
+
}
|
|
1464
|
+
this.attachPreflightTelemetry({
|
|
1465
|
+
name: input.name,
|
|
1466
|
+
label: input.label,
|
|
1467
|
+
method: input.method,
|
|
1468
|
+
url: input.url,
|
|
1469
|
+
headers,
|
|
1470
|
+
responseText,
|
|
1471
|
+
status: resp.status,
|
|
1472
|
+
success: resp.ok,
|
|
1473
|
+
durationMs: Date.now() - startedAt,
|
|
1474
|
+
extraAttrs: input.extraAttrs,
|
|
1475
|
+
extractAttrs: input.extractAttrs,
|
|
1476
|
+
});
|
|
1477
|
+
return { resp, responseText };
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Attach a redacted curl + request/response telemetry to the active
|
|
1481
|
+
* span for the core import/push endpoints (the `/pull` & `/push`
|
|
1482
|
+
* bundle routes and the records-mode `/view` reads & per-entity
|
|
1483
|
+
* writes). These already land in `aui curl` (via `captureRequest`)
|
|
1484
|
+
* and `.aui/push-logs/`, but were NOT visible in Logfire — so a
|
|
1485
|
+
* failed bundle push or a wrong-version pull showed only a span
|
|
1486
|
+
* with a bare error string. This makes the exact replayable curl +
|
|
1487
|
+
* (truncated) response body part of the trace, for BOTH success and
|
|
1488
|
+
* failure, in both records and bundle mode.
|
|
1489
|
+
*
|
|
1490
|
+
* Emits a `http.<name>` event (preserves history when several calls
|
|
1491
|
+
* share one span, e.g. the ~6 `/view` reads under a single
|
|
1492
|
+
* `aui.import`) AND mirrors the curl + HTTP fields onto the span
|
|
1493
|
+
* itself (so a dedicated per-call task span like
|
|
1494
|
+
* `aui.push.task.bundle` / `aui.push.task.<entity>` shows the curl
|
|
1495
|
+
* directly in its attributes). For multi-call spans the span-level
|
|
1496
|
+
* attrs are last-call-wins; the per-call events retain every curl.
|
|
1497
|
+
*
|
|
1498
|
+
* Best-effort throughout: telemetry must NEVER break the call path.
|
|
1499
|
+
*/
|
|
1500
|
+
attachHttpCurlTelemetry(input) {
|
|
1501
|
+
try {
|
|
1502
|
+
const span = trace.getActiveSpan();
|
|
1503
|
+
if (!span)
|
|
1504
|
+
return;
|
|
1505
|
+
const MAX = 32 * 1024;
|
|
1506
|
+
const truncatedResponse = input.responseText.length > MAX
|
|
1507
|
+
? input.responseText.slice(0, MAX) +
|
|
1508
|
+
`... [truncated ${input.responseText.length - MAX} bytes]`
|
|
1509
|
+
: input.responseText;
|
|
1510
|
+
const curl = formatAsCurlSafe({
|
|
1511
|
+
timestamp: new Date().toISOString(),
|
|
1512
|
+
method: input.method,
|
|
1513
|
+
url: input.url,
|
|
1514
|
+
headers: input.headers,
|
|
1515
|
+
body: input.requestBody,
|
|
1516
|
+
status: input.status,
|
|
1517
|
+
responseBody: input.responseText,
|
|
1518
|
+
label: input.label,
|
|
1519
|
+
success: input.success,
|
|
1520
|
+
}, { maxBodyBytes: MAX });
|
|
1521
|
+
const eventAttrs = {
|
|
1522
|
+
"http.method": input.method,
|
|
1523
|
+
"http.url": input.url,
|
|
1524
|
+
[`${input.name}.success`]: input.success,
|
|
1525
|
+
[`${input.name}.response_body`]: truncatedResponse,
|
|
1526
|
+
"aui.curl_repro": curl,
|
|
1527
|
+
};
|
|
1528
|
+
if (input.status > 0)
|
|
1529
|
+
eventAttrs["http.status_code"] = input.status;
|
|
1530
|
+
if (typeof input.durationMs === "number") {
|
|
1531
|
+
eventAttrs[`${input.name}.duration_ms`] = input.durationMs;
|
|
1532
|
+
}
|
|
1533
|
+
span.addEvent(`http.${input.name}`, eventAttrs);
|
|
1534
|
+
// Mirror onto the span for direct visibility on dedicated
|
|
1535
|
+
// per-call spans (push bundle / per-entity write task spans).
|
|
1536
|
+
span.setAttribute("aui.curl_repro", curl);
|
|
1537
|
+
span.setAttribute("http.method", input.method);
|
|
1538
|
+
span.setAttribute("http.url", input.url);
|
|
1539
|
+
span.setAttribute("http.response_body", truncatedResponse);
|
|
1540
|
+
if (input.status > 0) {
|
|
1541
|
+
span.setAttribute("http.status_code", input.status);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
catch {
|
|
1545
|
+
// Telemetry must never break the call path.
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
677
1548
|
agentManagementHeaders() {
|
|
678
1549
|
return {
|
|
679
1550
|
Accept: "application/json",
|
|
@@ -917,20 +1788,40 @@ export class AUIClient {
|
|
|
917
1788
|
console.log(`[debug] GET ${url}`);
|
|
918
1789
|
}
|
|
919
1790
|
const hdrs = this.agentSettingsReadHeaders();
|
|
1791
|
+
const startedAt = Date.now();
|
|
920
1792
|
const resp = await fetch(url, { method: "GET", headers: hdrs });
|
|
1793
|
+
// Read the body ONCE — used for telemetry, the error envelope, and
|
|
1794
|
+
// the success parse. node-fetch bodies are single-shot.
|
|
1795
|
+
const responseText = await resp.text();
|
|
921
1796
|
if (resp.status === 401 && !_isRetry) {
|
|
922
1797
|
const refreshed = await this.attemptTokenRefresh();
|
|
923
1798
|
if (refreshed) {
|
|
924
1799
|
return this.fetchV2(path, params, true);
|
|
925
1800
|
}
|
|
926
1801
|
}
|
|
1802
|
+
// Attach the replayable curl + response to the active span for the
|
|
1803
|
+
// records-mode `/view` reads `aui import` / `aui pull` issue (the
|
|
1804
|
+
// ~6 per-entity reads run under one `aui.import` / `aui.pull`
|
|
1805
|
+
// span). Success + failure, so the import endpoints are fully
|
|
1806
|
+
// visible in Logfire — not just `aui curl`.
|
|
1807
|
+
this.attachHttpCurlTelemetry({
|
|
1808
|
+
name: "import_view",
|
|
1809
|
+
label: `GET ${path}`,
|
|
1810
|
+
method: "GET",
|
|
1811
|
+
url,
|
|
1812
|
+
headers: hdrs,
|
|
1813
|
+
responseText,
|
|
1814
|
+
status: resp.status,
|
|
1815
|
+
success: resp.ok,
|
|
1816
|
+
durationMs: Date.now() - startedAt,
|
|
1817
|
+
});
|
|
927
1818
|
if (!resp.ok) {
|
|
928
1819
|
let errorBody;
|
|
929
1820
|
try {
|
|
930
|
-
errorBody =
|
|
1821
|
+
errorBody = JSON.parse(responseText);
|
|
931
1822
|
}
|
|
932
1823
|
catch {
|
|
933
|
-
errorBody =
|
|
1824
|
+
errorBody = responseText;
|
|
934
1825
|
}
|
|
935
1826
|
if (process.env.AUI_DEBUG) {
|
|
936
1827
|
console.log(`[debug] ${path} → ${resp.status}: ${JSON.stringify(errorBody)}`);
|
|
@@ -949,7 +1840,7 @@ export class AUIClient {
|
|
|
949
1840
|
err.statusCode = resp.status;
|
|
950
1841
|
throw err;
|
|
951
1842
|
}
|
|
952
|
-
const data =
|
|
1843
|
+
const data = responseText ? JSON.parse(responseText) : {};
|
|
953
1844
|
captureRequest({
|
|
954
1845
|
timestamp: new Date().toISOString(),
|
|
955
1846
|
method: "GET",
|
|
@@ -1017,11 +1908,22 @@ export class AUIClient {
|
|
|
1017
1908
|
}
|
|
1018
1909
|
return new URLSearchParams(filtered).toString();
|
|
1019
1910
|
}
|
|
1911
|
+
/**
|
|
1912
|
+
* Add agent-management identifiers (`agent_version_id` + `agent_id`) onto a
|
|
1913
|
+
* write body when present on `params`. All agent-settings CRUD methods —
|
|
1914
|
+
* parameters, scope-entities, rules, integrations, tools, general settings —
|
|
1915
|
+
* funnel through here, so the CLI is the single source of truth for both
|
|
1916
|
+
* fields. The BFF should NOT inject either; if you see it doing so for a
|
|
1917
|
+
* subset of routes (e.g. parameters / scope-entities / rules but not
|
|
1918
|
+
* integrations), that is a BFF bug to remove rather than mirror here.
|
|
1919
|
+
*/
|
|
1020
1920
|
versionBody(params, body) {
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1921
|
+
const enriched = { ...body };
|
|
1922
|
+
if (params.version_id)
|
|
1923
|
+
enriched.agent_version_id = params.version_id;
|
|
1924
|
+
if (params.agent_id)
|
|
1925
|
+
enriched.agent_id = params.agent_id;
|
|
1926
|
+
return enriched;
|
|
1025
1927
|
}
|
|
1026
1928
|
scopeBody(params) {
|
|
1027
1929
|
const fields = {};
|
|
@@ -1083,6 +1985,22 @@ export class AUIClient {
|
|
|
1083
1985
|
label: label || `${method} ${path}`,
|
|
1084
1986
|
success: resp.ok,
|
|
1085
1987
|
});
|
|
1988
|
+
// Attach the replayable curl + response to the active span for
|
|
1989
|
+
// BOTH success and failure (records-mode per-entity writes run
|
|
1990
|
+
// inside their own `aui.push.task.<entity>` span). Previously only
|
|
1991
|
+
// failures got a curl here; the owner asked for curl on every
|
|
1992
|
+
// pushed endpoint so triage never depends on `.aui/push-logs/`.
|
|
1993
|
+
this.attachHttpCurlTelemetry({
|
|
1994
|
+
name: "entity_write",
|
|
1995
|
+
label: label || `${method} ${path}`,
|
|
1996
|
+
method,
|
|
1997
|
+
url,
|
|
1998
|
+
headers,
|
|
1999
|
+
requestBody: body,
|
|
2000
|
+
responseText,
|
|
2001
|
+
status: resp.status,
|
|
2002
|
+
success: resp.ok,
|
|
2003
|
+
});
|
|
1086
2004
|
if (this.pushLogDir) {
|
|
1087
2005
|
this.writePushLog(method, url, headers, body, resp.status, responseText, label);
|
|
1088
2006
|
}
|
|
@@ -1307,6 +2225,7 @@ export class AUIClient {
|
|
|
1307
2225
|
const createBody = this.versionBody(params, {
|
|
1308
2226
|
name,
|
|
1309
2227
|
created_by: params.updated_by,
|
|
2228
|
+
type: item.type || "normal",
|
|
1310
2229
|
description: item.description || "",
|
|
1311
2230
|
identifier: item.identifier || null,
|
|
1312
2231
|
parameters: item.parameters || [],
|
|
@@ -1324,6 +2243,7 @@ export class AUIClient {
|
|
|
1324
2243
|
});
|
|
1325
2244
|
const patchBody = this.versionBody(params, {
|
|
1326
2245
|
updated_by: params.updated_by,
|
|
2246
|
+
type: item.type || null,
|
|
1327
2247
|
description: item.description || null,
|
|
1328
2248
|
identifier: item.identifier || null,
|
|
1329
2249
|
parameters: item.parameters || null,
|
|
@@ -1353,6 +2273,7 @@ export class AUIClient {
|
|
|
1353
2273
|
});
|
|
1354
2274
|
const code = item.code;
|
|
1355
2275
|
const createBody = this.versionBody(params, {
|
|
2276
|
+
code: code,
|
|
1356
2277
|
name: item.name || code,
|
|
1357
2278
|
type: item.type || "API",
|
|
1358
2279
|
settings: stripTestCurlResponse(item.settings || {}),
|
|
@@ -1370,6 +2291,7 @@ export class AUIClient {
|
|
|
1370
2291
|
enable_reduce_by_scope: "true",
|
|
1371
2292
|
});
|
|
1372
2293
|
const patchBody = this.versionBody(params, {
|
|
2294
|
+
name: item.name || null,
|
|
1373
2295
|
updated_by: params.updated_by,
|
|
1374
2296
|
description: item.description || null,
|
|
1375
2297
|
settings: item.settings ? stripTestCurlResponse(item.settings) : null,
|
|
@@ -1679,22 +2601,6 @@ export class AUIClient {
|
|
|
1679
2601
|
});
|
|
1680
2602
|
return this.writeV2("PUT", "agent-tools/rules", qs, bodyWithUser, "PUT rules");
|
|
1681
2603
|
}
|
|
1682
|
-
// ─── Widget (Card Template View Mode) ───
|
|
1683
|
-
/** POST /card-templates/view:generate — stateless server-side widget generation. */
|
|
1684
|
-
async generateWidget(params, body) {
|
|
1685
|
-
const enrichedBody = { ...body };
|
|
1686
|
-
if (params.network_id)
|
|
1687
|
-
enrichedBody.network_id = params.network_id;
|
|
1688
|
-
if (params.account_id)
|
|
1689
|
-
enrichedBody.account_id = params.account_id;
|
|
1690
|
-
if (params.organization_id)
|
|
1691
|
-
enrichedBody.organization_id = params.organization_id;
|
|
1692
|
-
if (params.network_category_id)
|
|
1693
|
-
enrichedBody.network_category_id = params.network_category_id;
|
|
1694
|
-
if (params.version_id)
|
|
1695
|
-
enrichedBody.agent_version_id = params.version_id;
|
|
1696
|
-
return (await this.writeV2("POST", "card-templates/view:generate", "", enrichedBody, "generate widget"));
|
|
1697
|
-
}
|
|
1698
2604
|
}
|
|
1699
2605
|
export function applyScopeLevel(params, level) {
|
|
1700
2606
|
if (!level || level === "network")
|