@agenthippo-ai/agenthippo 0.1.1 → 0.2.3-dataplane
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/out/agentpack/index.js +2 -1
- package/out/agentpack/paths.js +23 -0
- package/out/analytics/dashboard/fleetDisplayUtils.js +3 -0
- package/out/analytics/dashboard/webview/panel.js +290 -80
- package/out/analytics/sessionEvents.js +21 -9
- package/out/auth/brokerClient.js +1 -1
- package/out/auth/onboardingPrompt.js +112 -0
- package/out/auth/provider.js +1 -1
- package/out/auth-broker/config.js +8 -3
- package/out/auth-broker/server.js +36 -0
- package/out/cli/commands/ask.js +3 -0
- package/out/cli/commands/chat.js +3 -0
- package/out/cli/commands/deploy.js +49 -10
- package/out/cli/commands/gateway.js +54 -0
- package/out/cli/commands/idp.js +85 -0
- package/out/cli/commands/serve.js +315 -55
- package/out/cli/credential-context.js +42 -80
- package/out/cli/data-plane/broker.js +78 -0
- package/out/cli/data-plane/connector-host.js +121 -0
- package/out/cli/data-plane/egress.js +189 -0
- package/out/cli/data-plane/gateway.js +195 -0
- package/out/cli/data-plane/index.js +137 -0
- package/out/cli/data-plane/registry.js +80 -0
- package/out/cli/deploy/blueprints/registry.js +84 -0
- package/out/cli/deploy/layers/identity.js +91 -0
- package/out/cli/deploy-init.js +1105 -335
- package/out/cli/idp-init.js +303 -0
- package/out/cli/index.js +37 -7
- package/out/cli/pack-loader.js +37 -2
- package/out/cli/runtime.js +10 -3
- package/out/cli/serve-hardening.js +60 -1
- package/out/cli/state-sync.js +16 -15
- package/out/engines/claude.js +26 -5
- package/out/engines/codex.js +24 -5
- package/out/engines/custom.js +46 -12
- package/out/extension.js +2 -0
- package/out/gateway/outboundSend.js +6 -6
- package/out/headless/index.js +24 -9
- package/out/live-probe/connector.mjs +8 -0
- package/out/live-probe/env-report.mjs +8 -0
- package/out/mcp.js +85 -67
- package/out/scripts/analytics/agentide-litellm-otel.config.yaml +14 -0
- package/out/shared/models.js +1 -1
- package/out/shared/providers.js +2 -2
- package/out/test/analytics.test.js +42 -17
- package/out/test/auth-broker.test.js +53 -0
- package/out/test/blueprint-registry.test.js +105 -0
- package/out/test/credential-context.test.js +23 -28
- package/out/test/custom-env-layering.test.js +151 -0
- package/out/test/data-plane-engine-wiring.test.js +250 -0
- package/out/test/data-plane.test.js +244 -0
- package/out/test/deploy-init.test.js +255 -4
- package/out/test/egress-broker.test.js +172 -0
- package/out/test/fleetDisplayUtils.test.js +4 -0
- package/out/test/idp-init.test.js +151 -0
- package/out/test/mcp-obo.test.js +52 -27
- package/out/test/mcp.test.js +43 -3
- package/out/test/model-routing.test.js +133 -0
- package/out/test/serve-gateway-turn.test.js +267 -0
- package/out/test/serve-hardening.test.js +80 -0
- package/out/test/serveClient.test.js +32 -0
- package/out/test/state-sync-peer.test.js +18 -5
- package/out/utils.js +27 -11
- package/package.json +1 -1
|
@@ -51,7 +51,6 @@ exports.wrapEmitterWithObservability = wrapEmitterWithObservability;
|
|
|
51
51
|
const path = __importStar(require("path"));
|
|
52
52
|
const fs_1 = require("fs");
|
|
53
53
|
const branding_1 = require("../branding");
|
|
54
|
-
const credential_context_1 = require("../cli/credential-context");
|
|
55
54
|
const os = __importStar(require("os"));
|
|
56
55
|
// -----------------------------------------------------------------------------
|
|
57
56
|
// Session Event Writer
|
|
@@ -127,13 +126,25 @@ class ObservableStreamEmitter {
|
|
|
127
126
|
writer;
|
|
128
127
|
sessionId;
|
|
129
128
|
requestId;
|
|
129
|
+
userId;
|
|
130
130
|
missingCallIdCounter = 0;
|
|
131
131
|
pendingSyntheticCallIds = new Map();
|
|
132
|
-
constructor(inner, writer, sessionId, requestId
|
|
132
|
+
constructor(inner, writer, sessionId, requestId,
|
|
133
|
+
/**
|
|
134
|
+
* Acting user for the warrant audit trail on tool events. Captured once at
|
|
135
|
+
* wrap time (still inside the request's credential scope) and stored on this
|
|
136
|
+
* per-turn emitter — never re-read from ambient AsyncLocalStorage at emit
|
|
137
|
+
* time. Custom-engine persistent workers deliver tool events on a detached
|
|
138
|
+
* parent IPC callback whose ALS can stick to the spawn request; constructor
|
|
139
|
+
* capture keeps attribution correct for every engine. Undefined outside a
|
|
140
|
+
* serve credential scope (IDE-local / CLI without runWithCredential).
|
|
141
|
+
*/
|
|
142
|
+
userId) {
|
|
133
143
|
this.inner = inner;
|
|
134
144
|
this.writer = writer;
|
|
135
145
|
this.sessionId = sessionId;
|
|
136
146
|
this.requestId = requestId;
|
|
147
|
+
this.userId = userId;
|
|
137
148
|
}
|
|
138
149
|
makeCallIdKey(toolName) {
|
|
139
150
|
return `${this.sessionId}:${this.requestId}:${toolName}`;
|
|
@@ -176,9 +187,8 @@ class ObservableStreamEmitter {
|
|
|
176
187
|
callId: loggedCallId,
|
|
177
188
|
// Truncate input to avoid huge JSONL files
|
|
178
189
|
inputPreview: input?.slice(0, 500),
|
|
179
|
-
// The warrant: who this tool call ran as
|
|
180
|
-
|
|
181
|
-
userId: (0, credential_context_1.currentCredential)()?.userId,
|
|
190
|
+
// The warrant: who this tool call ran as (captured at wrap; see constructor).
|
|
191
|
+
userId: this.userId,
|
|
182
192
|
});
|
|
183
193
|
return this.inner.toolStart(toolName, input, toolCallId, metadata);
|
|
184
194
|
}
|
|
@@ -192,7 +202,7 @@ class ObservableStreamEmitter {
|
|
|
192
202
|
isError: isError ?? false,
|
|
193
203
|
// Truncate result to avoid huge JSONL files
|
|
194
204
|
resultPreview: result?.slice(0, 500),
|
|
195
|
-
userId:
|
|
205
|
+
userId: this.userId,
|
|
196
206
|
});
|
|
197
207
|
return this.inner.toolEnd(toolName, toolCallId, result, isError, metadata);
|
|
198
208
|
}
|
|
@@ -205,6 +215,7 @@ class ObservableStreamEmitter {
|
|
|
205
215
|
async error(message) {
|
|
206
216
|
await this.writer.append(this.sessionId, this.requestId, 'error', {
|
|
207
217
|
message,
|
|
218
|
+
userId: this.userId,
|
|
208
219
|
});
|
|
209
220
|
return this.inner.error(message);
|
|
210
221
|
}
|
|
@@ -215,7 +226,7 @@ class ObservableStreamEmitter {
|
|
|
215
226
|
* Emit a turn_start event. Call at the beginning of stream().
|
|
216
227
|
*/
|
|
217
228
|
async emitTurnStart() {
|
|
218
|
-
await this.writer.append(this.sessionId, this.requestId, 'turn_start', {});
|
|
229
|
+
await this.writer.append(this.sessionId, this.requestId, 'turn_start', { userId: this.userId });
|
|
219
230
|
}
|
|
220
231
|
/**
|
|
221
232
|
* Emit a turn_end event. Call at the end of stream().
|
|
@@ -223,6 +234,7 @@ class ObservableStreamEmitter {
|
|
|
223
234
|
async emitTurnEnd(success) {
|
|
224
235
|
await this.writer.append(this.sessionId, this.requestId, 'turn_end', {
|
|
225
236
|
success,
|
|
237
|
+
userId: this.userId,
|
|
226
238
|
});
|
|
227
239
|
}
|
|
228
240
|
}
|
|
@@ -231,6 +243,6 @@ exports.ObservableStreamEmitter = ObservableStreamEmitter;
|
|
|
231
243
|
* Create an ObservableStreamEmitter that wraps the given emitter.
|
|
232
244
|
* Returns both the wrapped emitter and emit helpers for turn boundaries.
|
|
233
245
|
*/
|
|
234
|
-
function wrapEmitterWithObservability(inner, writer, sessionId, requestId) {
|
|
235
|
-
return new ObservableStreamEmitter(inner, writer, sessionId, requestId);
|
|
246
|
+
function wrapEmitterWithObservability(inner, writer, sessionId, requestId, userId) {
|
|
247
|
+
return new ObservableStreamEmitter(inner, writer, sessionId, requestId, userId);
|
|
236
248
|
}
|
package/out/auth/brokerClient.js
CHANGED
|
@@ -118,7 +118,7 @@ function buildAuthorizeUrl(baseUrl, redirectUri, codeChallenge, state, loginHint
|
|
|
118
118
|
}
|
|
119
119
|
return url.toString();
|
|
120
120
|
}
|
|
121
|
-
async function completeDevPkceLogin(baseUrl, email, redirectUri = '
|
|
121
|
+
async function completeDevPkceLogin(baseUrl, email, redirectUri = 'http://127.0.0.1:1/callback') {
|
|
122
122
|
const verifier = (0, pkce_js_1.generateCodeVerifier)();
|
|
123
123
|
const challenge = (0, pkce_js_1.codeChallengeFromVerifier)(verifier);
|
|
124
124
|
const code = await devLoginForTests(baseUrl, email, redirectUri, challenge);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) AgentHippo.ai. All rights reserved.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.showSignInOnboardingPromptIfNeeded = showSignInOnboardingPromptIfNeeded;
|
|
40
|
+
exports.scheduleSignInOnboardingPrompt = scheduleSignInOnboardingPrompt;
|
|
41
|
+
const vscode = __importStar(require("vscode"));
|
|
42
|
+
const sessionStore_js_1 = require("./sessionStore.js");
|
|
43
|
+
const PROMPT_DISMISSED_KEY = 'agentide.signInPrompt.dismissed';
|
|
44
|
+
const PROVIDERS_LIST_KEY = 'agentide.providers';
|
|
45
|
+
const API_KEY_PREFIX = 'agentide.apikey.';
|
|
46
|
+
const OAUTH_PREFIX = 'agentide.oauth.';
|
|
47
|
+
const ONBOARDING_MESSAGE = 'BYOK or Sign in for instant model access — no API key needed.';
|
|
48
|
+
async function hasByokConfigured(secrets) {
|
|
49
|
+
const providersJson = await secrets.get(PROVIDERS_LIST_KEY);
|
|
50
|
+
if (!providersJson) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const providers = JSON.parse(providersJson);
|
|
55
|
+
if (!Array.isArray(providers)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
for (const provider of providers) {
|
|
59
|
+
if (provider === 'agenthippo') {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const apiKey = await secrets.get(`${API_KEY_PREFIX}${provider}`);
|
|
63
|
+
if (apiKey?.trim()) {
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
const oauth = await secrets.get(`${OAUTH_PREFIX}${provider}`);
|
|
67
|
+
if (oauth?.trim()) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// ignore malformed provider list
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
async function shouldShowOnboardingPrompt(context) {
|
|
78
|
+
if (context.globalState.get(PROMPT_DISMISSED_KEY)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
if (await (0, sessionStore_js_1.getStoredAuthSession)(context.secrets)) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
if (await hasByokConfigured(context.secrets)) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
async function showSignInOnboardingPromptIfNeeded(context) {
|
|
90
|
+
if (!(await shouldShowOnboardingPrompt(context))) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const choice = await vscode.window.showInformationMessage(ONBOARDING_MESSAGE, 'Sign In', 'Add API Key', 'Later');
|
|
94
|
+
if (choice === 'Sign In') {
|
|
95
|
+
await vscode.commands.executeCommand('agenthippo.signIn');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (choice === 'Add API Key') {
|
|
99
|
+
await vscode.commands.executeCommand('agentide.addAPIKey');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (choice === 'Later' || choice === undefined) {
|
|
103
|
+
await context.globalState.update(PROMPT_DISMISSED_KEY, true);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Delay so the workbench finishes restoring before the toast appears. */
|
|
107
|
+
function scheduleSignInOnboardingPrompt(context) {
|
|
108
|
+
const handle = setTimeout(() => {
|
|
109
|
+
void showSignInOnboardingPromptIfNeeded(context);
|
|
110
|
+
}, 3000);
|
|
111
|
+
context.subscriptions.push({ dispose: () => clearTimeout(handle) });
|
|
112
|
+
}
|
package/out/auth/provider.js
CHANGED
|
@@ -48,7 +48,7 @@ const signInFlow_js_1 = require("./signInFlow.js");
|
|
|
48
48
|
const sessionStore_js_1 = require("./sessionStore.js");
|
|
49
49
|
var sessionStore_js_2 = require("./sessionStore.js");
|
|
50
50
|
Object.defineProperty(exports, "getAgentHippoAccessToken", { enumerable: true, get: function () { return sessionStore_js_2.getAgentHippoAccessToken; } });
|
|
51
|
-
const DEFAULT_AGENTHIPPO_MODEL_GATEWAY_URL = 'https://
|
|
51
|
+
const DEFAULT_AGENTHIPPO_MODEL_GATEWAY_URL = 'https://gateway.agenthippo.ai/v1';
|
|
52
52
|
function getAgentHippoModelGatewayUrl() {
|
|
53
53
|
return vscode.workspace.getConfiguration('agentide').get('AgentHippoModelGateway.url')?.trim() || DEFAULT_AGENTHIPPO_MODEL_GATEWAY_URL;
|
|
54
54
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Copyright (c) AgentHippo.ai. All rights reserved.
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.DEFAULT_JWT_SECRET = void 0;
|
|
6
|
+
exports.DEFAULT_LITELLM_API_KEY = exports.DEFAULT_JWT_SECRET = void 0;
|
|
7
7
|
exports.loadAuthBrokerConfig = loadAuthBrokerConfig;
|
|
8
8
|
exports.emailDomainAllowed = emailDomainAllowed;
|
|
9
9
|
function parseDomains(raw) {
|
|
@@ -14,6 +14,8 @@ function parseDomains(raw) {
|
|
|
14
14
|
}
|
|
15
15
|
/** Well-known default secret used in dev/test. Never acceptable in production. */
|
|
16
16
|
exports.DEFAULT_JWT_SECRET = 'dev-agenthippo-auth-secret-change-me';
|
|
17
|
+
/** Well-known default LiteLLM team key used in dev/test. Never acceptable in production. */
|
|
18
|
+
exports.DEFAULT_LITELLM_API_KEY = 'ahk_team_dev_key';
|
|
17
19
|
function loadAuthBrokerConfig(env = process.env) {
|
|
18
20
|
const jwtSecret = env.AGENTHIPPO_AUTH_JWT_SECRET?.trim() || exports.DEFAULT_JWT_SECRET;
|
|
19
21
|
const publicBaseUrl = (env.AGENTHIPPO_AUTH_PUBLIC_URL?.trim() || `http://127.0.0.1:${env.AGENTHIPPO_AUTH_PORT?.trim() || '9780'}`).replace(/\/+$/, '');
|
|
@@ -27,7 +29,10 @@ function loadAuthBrokerConfig(env = process.env) {
|
|
|
27
29
|
accessTokenTtlSec: Number(env.AGENTHIPPO_AUTH_ACCESS_TTL_SEC || 3600),
|
|
28
30
|
refreshTokenTtlSec: Number(env.AGENTHIPPO_AUTH_REFRESH_TTL_SEC || 30 * 24 * 3600),
|
|
29
31
|
allowedEmailDomains: parseDomains(env.AGENTHIPPO_AUTH_ALLOWED_DOMAINS),
|
|
30
|
-
|
|
32
|
+
// Deliberately not gated on NODE_ENV=test: that value is too easy to inherit by accident
|
|
33
|
+
// (test runners, base images, wrapper scripts) and this flag unlocks an unauthenticated
|
|
34
|
+
// login bypass (/oauth/dev/login, login_hint auto-auth) — require the explicit opt-in.
|
|
35
|
+
devMode: env.AGENTHIPPO_AUTH_DEV === '1',
|
|
31
36
|
rateLimitPerMin: env.AGENTHIPPO_AUTH_RATE_LIMIT !== undefined ? Number(env.AGENTHIPPO_AUTH_RATE_LIMIT) : 60,
|
|
32
37
|
logPii: env.AGENTHIPPO_AUTH_LOG_PII === '1',
|
|
33
38
|
cookieName: env.AGENTHIPPO_AUTH_COOKIE_NAME?.trim() || 'ah_token',
|
|
@@ -41,7 +46,7 @@ function loadAuthBrokerConfig(env = process.env) {
|
|
|
41
46
|
storeIndexUrl: env.AGENTHIPPO_TEAM_STORE_INDEX_URL?.trim() || `https://${storeOwner}.github.io/${storeName}`,
|
|
42
47
|
githubOrg: env.AGENTHIPPO_TEAM_GITHUB_ORG?.trim() || storeOwner,
|
|
43
48
|
litellmBaseUrl: env.AGENTHIPPO_TEAM_LITELLM_BASE_URL?.trim() || 'http://127.0.0.1:4000',
|
|
44
|
-
litellmApiKey: env.AGENTHIPPO_TEAM_LITELLM_API_KEY?.trim() ||
|
|
49
|
+
litellmApiKey: env.AGENTHIPPO_TEAM_LITELLM_API_KEY?.trim() || exports.DEFAULT_LITELLM_API_KEY,
|
|
45
50
|
},
|
|
46
51
|
};
|
|
47
52
|
}
|
|
@@ -47,7 +47,29 @@ const store_js_1 = require("./store.js");
|
|
|
47
47
|
const pkce_js_1 = require("./pkce.js");
|
|
48
48
|
const jwt_js_1 = require("./jwt.js");
|
|
49
49
|
const types_js_1 = require("../auth/types.js");
|
|
50
|
+
const callbackUriParse_js_1 = require("../auth/callbackUriParse.js");
|
|
50
51
|
const serve_hardening_js_1 = require("../cli/serve-hardening.js");
|
|
52
|
+
/**
|
|
53
|
+
* Restrict `redirect_uri` to the shapes our own clients actually send — the CLI/IDE loopback
|
|
54
|
+
* callback (127.0.0.1/localhost, any OS-assigned port, see loopbackAuthServer.ts) and the IDE
|
|
55
|
+
* URI-scheme callback (any editor scheme, fixed authority+path, see callbackUri.ts). Without
|
|
56
|
+
* this, `/oauth/authorize` would hand a real auth code to whatever `redirect_uri` a caller
|
|
57
|
+
* supplies — an attacker can send a victim (who has an active SSO session) a link with their
|
|
58
|
+
* own redirect_uri and code_challenge and steal the resulting code via their own server.
|
|
59
|
+
*/
|
|
60
|
+
function isAllowedRedirectUri(redirectUri) {
|
|
61
|
+
let target;
|
|
62
|
+
try {
|
|
63
|
+
target = new url_1.URL(redirectUri);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (target.protocol === 'http:') {
|
|
69
|
+
return (target.hostname === '127.0.0.1' || target.hostname === 'localhost') && target.pathname === '/callback';
|
|
70
|
+
}
|
|
71
|
+
return (0, callbackUriParse_js_1.isAuthCallbackParts)(target.host, target.pathname);
|
|
72
|
+
}
|
|
51
73
|
function sendJson(res, status, body) {
|
|
52
74
|
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
53
75
|
res.end(JSON.stringify(body));
|
|
@@ -114,6 +136,9 @@ function extractCookieToken(cookieHeader, name) {
|
|
|
114
136
|
function safeReturnTo(rawReturnTo, cookieDomain, fallback) {
|
|
115
137
|
try {
|
|
116
138
|
const target = new url_1.URL(rawReturnTo);
|
|
139
|
+
if (target.protocol !== 'http:' && target.protocol !== 'https:') {
|
|
140
|
+
return fallback;
|
|
141
|
+
}
|
|
117
142
|
if (cookieDomain) {
|
|
118
143
|
const parent = cookieDomain.replace(/^\./, '').toLowerCase();
|
|
119
144
|
if (target.hostname.toLowerCase() === parent || target.hostname.toLowerCase().endsWith('.' + parent)) {
|
|
@@ -136,6 +161,12 @@ function createAuthBrokerServer(config) {
|
|
|
136
161
|
throw new Error('AGENTHIPPO_AUTH_JWT_SECRET must be set to a strong secret in production. ' +
|
|
137
162
|
'Generate one with: openssl rand -hex 32');
|
|
138
163
|
}
|
|
164
|
+
// Fail-closed: a known-default LiteLLM key would hand every team member the same
|
|
165
|
+
// publicly-known credential if the operator forgot to set the real one.
|
|
166
|
+
if (!resolved.devMode && resolved.team.litellmApiKey === config_js_1.DEFAULT_LITELLM_API_KEY) {
|
|
167
|
+
throw new Error('AGENTHIPPO_TEAM_LITELLM_API_KEY must be set in production. ' +
|
|
168
|
+
'The default dev key must not be issued to real users via bootstrap.');
|
|
169
|
+
}
|
|
139
170
|
// Warn when no domain restriction is configured: any SSO-authenticated email can sign in.
|
|
140
171
|
if (!resolved.devMode && resolved.allowedEmailDomains.length === 0) {
|
|
141
172
|
console.warn('[auth-broker] Warning: AGENTHIPPO_AUTH_ALLOWED_DOMAINS is not set — ' +
|
|
@@ -256,6 +287,11 @@ function createAuthBrokerServer(config) {
|
|
|
256
287
|
sendJson(res, 400, { error: 'invalid_request' });
|
|
257
288
|
return;
|
|
258
289
|
}
|
|
290
|
+
if (!isAllowedRedirectUri(redirectUri)) {
|
|
291
|
+
audit('authorize.invalid_redirect_uri', { client_id: clientId });
|
|
292
|
+
sendJson(res, 400, { error: 'invalid_request', error_description: 'redirect_uri not allowed' });
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
259
295
|
// Dev mode with explicit login_hint: auto-authenticate (backward compat for tests).
|
|
260
296
|
if (resolved.devMode && loginHint) {
|
|
261
297
|
const user = store.upsertUser(loginHint);
|
package/out/cli/commands/ask.js
CHANGED
|
@@ -173,6 +173,9 @@ async function askCommand(options) {
|
|
|
173
173
|
apiKey: apiConfig.apiKey,
|
|
174
174
|
baseUrl: apiConfig.baseUrl,
|
|
175
175
|
useLiteLLM: apiConfig.useLiteLLM,
|
|
176
|
+
litellmApiKey: apiConfig.litellmApiKey,
|
|
177
|
+
openaiApiKey: apiConfig.openaiApiKey,
|
|
178
|
+
anthropicApiKey: apiConfig.anthropicApiKey,
|
|
176
179
|
logger,
|
|
177
180
|
freshState: !options.sessionId?.trim(), // Persist native session when continuing a conversation
|
|
178
181
|
permissionHandler,
|
package/out/cli/commands/chat.js
CHANGED
|
@@ -152,6 +152,9 @@ async function chatCommand(options) {
|
|
|
152
152
|
apiKey: apiConfig.apiKey,
|
|
153
153
|
baseUrl: apiConfig.baseUrl,
|
|
154
154
|
useLiteLLM: apiConfig.useLiteLLM,
|
|
155
|
+
litellmApiKey: apiConfig.litellmApiKey,
|
|
156
|
+
openaiApiKey: apiConfig.openaiApiKey,
|
|
157
|
+
anthropicApiKey: apiConfig.anthropicApiKey,
|
|
155
158
|
logger,
|
|
156
159
|
freshState: false, // Persist session within REPL
|
|
157
160
|
claudeCodeExecutablePath: claudeCodePath,
|
|
@@ -48,9 +48,10 @@ const fs = __importStar(require("fs"));
|
|
|
48
48
|
const path = __importStar(require("path"));
|
|
49
49
|
const yaml = __importStar(require("js-yaml"));
|
|
50
50
|
const deploy_init_1 = require("../deploy-init");
|
|
51
|
+
const registry_1 = require("../deploy/blueprints/registry");
|
|
51
52
|
const utils_1 = require("../utils");
|
|
52
53
|
const DEFAULT_PORT = 8080;
|
|
53
|
-
/** Read pack id + engine + model from agent.yaml without the full loader (no workspace/sync needed). */
|
|
54
|
+
/** Read pack id + version + engine + model from agent.yaml without the full loader (no workspace/sync needed). */
|
|
54
55
|
function readManifest(packDir) {
|
|
55
56
|
const manifestPath = path.join(packDir, 'agent.yaml');
|
|
56
57
|
if (!fs.existsSync(manifestPath)) {
|
|
@@ -61,9 +62,14 @@ function readManifest(packDir) {
|
|
|
61
62
|
if (!name) {
|
|
62
63
|
throw new Error(`agent.yaml is missing metadata.name in ${packDir}`);
|
|
63
64
|
}
|
|
65
|
+
const version = parsed?.metadata?.version?.trim();
|
|
66
|
+
if (!version) {
|
|
67
|
+
throw new Error(`agent.yaml is missing metadata.version in ${packDir}`);
|
|
68
|
+
}
|
|
64
69
|
const model = parsed?.spec?.model?.trim();
|
|
65
70
|
return {
|
|
66
71
|
name,
|
|
72
|
+
version,
|
|
67
73
|
engine: parsed?.spec?.engine?.trim() || 'claude',
|
|
68
74
|
...(model ? { model } : {}),
|
|
69
75
|
};
|
|
@@ -123,7 +129,7 @@ function copyPackToStaging(packDir, stagingDir) {
|
|
|
123
129
|
return false;
|
|
124
130
|
if (base.endsWith('.log'))
|
|
125
131
|
return false;
|
|
126
|
-
if (
|
|
132
|
+
if ((0, deploy_init_1.isSecretLikeBasename)(base))
|
|
127
133
|
return false;
|
|
128
134
|
return true;
|
|
129
135
|
},
|
|
@@ -135,8 +141,35 @@ async function deployCommand(action, packArg, options) {
|
|
|
135
141
|
console.error(`Unknown deploy action "${action}". Supported: init`);
|
|
136
142
|
process.exit(1);
|
|
137
143
|
}
|
|
144
|
+
// Blueprint resolution (PLAN/deploy.md §3) picks --target/--ingress for you; --platform
|
|
145
|
+
// selects which platform within the blueprint's supported set. Raw --target/--ingress stay
|
|
146
|
+
// fully usable when --blueprint is omitted — nothing here changes that path.
|
|
147
|
+
let resolvedTarget = options.target;
|
|
148
|
+
let resolvedIngress = options.ingress;
|
|
149
|
+
if (options.blueprint) {
|
|
150
|
+
if (!(0, registry_1.isBlueprintId)(options.blueprint)) {
|
|
151
|
+
console.error(`Unknown --blueprint "${options.blueprint}". Supported: ${Object.keys(registry_1.BLUEPRINTS).join(', ')}`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (options.platform && !(0, deploy_init_1.isDeployTarget)(options.platform)) {
|
|
156
|
+
console.error(`Unknown --platform "${options.platform}". Supported: ${deploy_init_1.DEPLOY_TARGETS.join(', ')}`);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const resolved = (0, registry_1.resolveBlueprint)(options.blueprint, options.platform);
|
|
162
|
+
resolvedTarget = resolved.target;
|
|
163
|
+
resolvedIngress = resolved.ingress;
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
console.error(`Error: ${err instanceof Error ? err.message : err}`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
138
171
|
const packDir = path.resolve(options.packdir || packArg || process.cwd());
|
|
139
|
-
const targetsRequested =
|
|
172
|
+
const targetsRequested = resolvedTarget === 'all' ? [...deploy_init_1.DEPLOY_TARGETS] : [resolvedTarget];
|
|
140
173
|
for (const t of targetsRequested) {
|
|
141
174
|
if (!(0, deploy_init_1.isDeployTarget)(t)) {
|
|
142
175
|
console.error(`Unknown --target "${t}". Supported: ${deploy_init_1.DEPLOY_TARGETS.join(', ')}, all`);
|
|
@@ -162,8 +195,14 @@ async function deployCommand(action, packArg, options) {
|
|
|
162
195
|
}
|
|
163
196
|
}
|
|
164
197
|
}
|
|
198
|
+
const modelRouting = options.modelRouting === 'gateway' ? 'gateway' : options.modelRouting === 'sidecar' ? 'sidecar' : undefined;
|
|
199
|
+
if (options.modelRouting && options.modelRouting !== 'gateway' && options.modelRouting !== 'sidecar') {
|
|
200
|
+
console.error(`Unknown --model-routing "${options.modelRouting}". Supported: sidecar, gateway`);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
165
203
|
const ctx = {
|
|
166
204
|
packName: manifest.name,
|
|
205
|
+
packVersion: manifest.version,
|
|
167
206
|
image: options.image || (0, deploy_init_1.defaultRuntimeImage)(manifest.engine),
|
|
168
207
|
port: options.port || DEFAULT_PORT,
|
|
169
208
|
engine: manifest.engine,
|
|
@@ -171,18 +210,17 @@ async function deployCommand(action, packArg, options) {
|
|
|
171
210
|
envVars,
|
|
172
211
|
gatewayChannels: gateway,
|
|
173
212
|
publicHostname: (0, deploy_init_1.resolveDeployPublicHostname)(options.hostname),
|
|
174
|
-
ingress:
|
|
213
|
+
ingress: resolvedIngress,
|
|
175
214
|
idpUrl: options.idpUrl,
|
|
176
215
|
cookieDomain: options.cookieDomain,
|
|
216
|
+
colocate: options.colocate,
|
|
217
|
+
modelRouting,
|
|
177
218
|
};
|
|
178
219
|
for (const target of targetsRequested) {
|
|
179
|
-
|
|
180
|
-
// ./deploy-out/<pack>-<target> (cwd-relative); --out overrides (per-target subdir if
|
|
181
|
-
// multiple targets). Build context = staging root, so COPY . /app/pack/ is correct
|
|
182
|
-
// regardless of where the script is invoked from.
|
|
220
|
+
const routingSuffix = modelRouting === 'gateway' ? '-gateway' : '';
|
|
183
221
|
const outDir = options.out
|
|
184
|
-
? (targetsRequested.length > 1 ? path.join(path.resolve(options.out), target) : path.resolve(options.out))
|
|
185
|
-
: path.join(process.cwd(), 'deploy-out', `${manifest.name}-${target}`);
|
|
222
|
+
? (targetsRequested.length > 1 ? path.join(path.resolve(options.out), target + routingSuffix) : path.resolve(options.out))
|
|
223
|
+
: path.join(process.cwd(), 'deploy-out', `${manifest.name}-${target}${routingSuffix}`);
|
|
186
224
|
fs.mkdirSync(outDir, { recursive: true });
|
|
187
225
|
// Copy the pack into the staging root (filtered: no VCS/caches/secrets, no source deploy/).
|
|
188
226
|
const brokenLinks = copyPackToStaging(packDir, outDir);
|
|
@@ -220,4 +258,5 @@ async function deployCommand(action, packArg, options) {
|
|
|
220
258
|
console.log(`\n${utils_1.COLORS.dim}Scanned pack secrets into .env.example: ${envVars.join(', ')}${utils_1.COLORS.reset}`);
|
|
221
259
|
}
|
|
222
260
|
console.log(`\nNext: cd into the staging dir above, \`cp deploy/.env.example deploy/.env\`, then run the command in deploy/README.md (scripts anchor themselves to the staging root).`);
|
|
261
|
+
console.log(`${utils_1.COLORS.dim}Tip: run deploy init from the pack's parent directory — not from inside the pack (default deploy-out/ inside the pack copies into itself and fails).${utils_1.COLORS.reset}`);
|
|
223
262
|
}
|
|
@@ -60,7 +60,61 @@ async function ensureStoreFileExists(storePath) {
|
|
|
60
60
|
process.exit(1);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Broker-mediated send (secure-obo-design §7.6): under `agenthippo serve` with the egress
|
|
65
|
+
* broker active, the agent's env carries AGENTHIPPO_EGRESS_URL + AGENTHIPPO_TURN_NONCE but NOT
|
|
66
|
+
* the gateway token. Route the send through the broker, which resolves the destination
|
|
67
|
+
* server-side from THIS turn's bound handles and delivers with the broker-held token.
|
|
68
|
+
* `--outbound`/`--agent` are ignored here — the broker derives both from the verified nonce.
|
|
69
|
+
*/
|
|
70
|
+
async function sendViaEgressBroker(egressUrl, nonce, options) {
|
|
71
|
+
const message = options.message.trim();
|
|
72
|
+
if (!message) {
|
|
73
|
+
console.error('Error: --message is required');
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
if (options.dryRun) {
|
|
77
|
+
console.log(`${utils_js_1.COLORS.dim}Dry run (egress broker):${utils_js_1.COLORS.reset} POST ${egressUrl} { to: ${options.target ?? '(reply-to-sender)'} }`);
|
|
78
|
+
process.exit(0);
|
|
79
|
+
}
|
|
80
|
+
let res;
|
|
81
|
+
try {
|
|
82
|
+
res = await fetch(egressUrl, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${nonce}` },
|
|
85
|
+
body: JSON.stringify({ message, to: options.target?.trim() || undefined, media: options.media?.trim() || undefined }),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
console.error(`${utils_js_1.COLORS.red}✗${utils_js_1.COLORS.reset} Egress broker unreachable: ${err instanceof Error ? err.message : String(err)}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
const bodyText = await res.text();
|
|
93
|
+
let body = {};
|
|
94
|
+
try {
|
|
95
|
+
body = JSON.parse(bodyText);
|
|
96
|
+
}
|
|
97
|
+
catch { /* non-JSON error body */ }
|
|
98
|
+
if (options.json) {
|
|
99
|
+
console.log(JSON.stringify({ via: 'egress-broker', status: res.status, result: body }, null, 2));
|
|
100
|
+
}
|
|
101
|
+
else if (res.ok && body.ok) {
|
|
102
|
+
console.log(`${utils_js_1.COLORS.green}✓${utils_js_1.COLORS.reset} Sent (via egress broker${body.via ? `, ${body.via}` : ''})`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
console.error(`${utils_js_1.COLORS.red}✗${utils_js_1.COLORS.reset} Egress send rejected (HTTP ${res.status}): ${body.error || bodyText || 'unknown error'}`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
process.exit(res.ok && body.ok ? 0 : 1);
|
|
109
|
+
}
|
|
63
110
|
async function gatewaySendCommand(options) {
|
|
111
|
+
// Prefer the broker whenever serve exposes it: the agent has no gateway token to send directly.
|
|
112
|
+
const egressUrl = process.env.AGENTHIPPO_EGRESS_URL?.trim();
|
|
113
|
+
const nonce = process.env.AGENTHIPPO_TURN_NONCE?.trim();
|
|
114
|
+
if (egressUrl && nonce) {
|
|
115
|
+
await sendViaEgressBroker(egressUrl, nonce, options);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
64
118
|
const storePath = resolveOutboundPath(options.outbound);
|
|
65
119
|
await ensureStoreFileExists(storePath);
|
|
66
120
|
const agentId = options.agent.trim();
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) AgentHippo.ai. All rights reserved.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.idpCommand = idpCommand;
|
|
40
|
+
/**
|
|
41
|
+
* IdP Command — `agenthippo idp init --out <path> --hostname <host>`
|
|
42
|
+
*
|
|
43
|
+
* Generates the central IdP stack (PLAN/deploy.md) into a self-contained directory. Generates
|
|
44
|
+
* only; never builds images or calls a cloud — same rule as `deploy init`.
|
|
45
|
+
*/
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const idp_init_1 = require("../idp-init");
|
|
49
|
+
const utils_1 = require("../utils");
|
|
50
|
+
async function idpCommand(action, options) {
|
|
51
|
+
if (action !== 'init') {
|
|
52
|
+
console.error(`Unknown idp action "${action}". Supported: init`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!options.hostname?.trim()) {
|
|
57
|
+
console.error('idp init requires --hostname <public-hostname> (e.g. auth.corp.example)');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const outDir = path.resolve(options.out || './enterprise-deploy/idp');
|
|
62
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
63
|
+
const ctx = { hostname: options.hostname.trim(), cookieDomain: options.cookieDomain };
|
|
64
|
+
const files = (0, idp_init_1.renderIdpArtifacts)(ctx);
|
|
65
|
+
const written = [];
|
|
66
|
+
const skipped = [];
|
|
67
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
68
|
+
const dest = path.join(outDir, rel);
|
|
69
|
+
if (fs.existsSync(dest) && !options.force) {
|
|
70
|
+
skipped.push(rel);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
74
|
+
fs.writeFileSync(dest, content);
|
|
75
|
+
written.push(rel);
|
|
76
|
+
}
|
|
77
|
+
console.log(`${utils_1.COLORS.green}✓${utils_1.COLORS.reset} idp → ${path.relative(process.cwd(), outDir) || '.'}`);
|
|
78
|
+
for (const f of written) {
|
|
79
|
+
console.log(` ${utils_1.COLORS.dim}+ ${f}${utils_1.COLORS.reset}`);
|
|
80
|
+
}
|
|
81
|
+
for (const f of skipped) {
|
|
82
|
+
console.log(` ${utils_1.COLORS.yellow}• ${f} (exists; --force to overwrite)${utils_1.COLORS.reset}`);
|
|
83
|
+
}
|
|
84
|
+
console.log(`Next: cd ${path.relative(process.cwd(), outDir) || '.'}, cp .env.example ~/.agent-hippo/.env, cp .env.auth.example ~/.agent-hippo/.env.auth (fill in), then ./run.sh`);
|
|
85
|
+
}
|