@agenthippo-ai/agenthippo 0.1.1 → 1.110.9
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/provider.js +13 -2
- package/out/analytics/dashboard/webview/panel.js +293 -82
- package/out/analytics/identity.js +0 -0
- package/out/analytics/service.js +23 -25
- package/out/analytics/sessionEvents.js +109 -22
- package/out/auth/authBaseUrl.js +59 -0
- package/out/auth/bootstrapApply.js +9 -10
- package/out/auth/brokerClient.js +49 -15
- package/out/auth/onboardingPrompt.js +112 -0
- package/out/auth/provider.js +9 -22
- package/out/auth/sessionStore.js +3 -40
- package/out/auth/signInFlow.js +2 -1
- package/out/auth-broker/config.js +54 -4
- package/out/auth-broker/server.js +152 -20
- package/out/auth-broker/store.js +25 -24
- package/out/authority/warrant.js +206 -0
- package/out/branding.js +27 -1
- package/out/cli/commands/ask.js +3 -0
- package/out/cli/commands/chat.js +3 -0
- package/out/cli/commands/conformance.js +249 -0
- package/out/cli/commands/data-plane.js +67 -0
- package/out/cli/commands/deploy.js +50 -10
- package/out/cli/commands/gateway.js +58 -0
- package/out/cli/commands/idp.js +85 -0
- package/out/cli/commands/pack.js +182 -3
- package/out/cli/commands/sandbox.js +332 -0
- package/out/cli/commands/serve.js +1165 -82
- package/out/cli/commands/store.js +12 -4
- 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 +218 -0
- package/out/cli/data-plane/index.js +152 -0
- package/out/cli/data-plane/registry.js +89 -0
- package/out/cli/data-plane/remote.js +106 -0
- package/out/cli/data-plane/sidecar.js +161 -0
- package/out/cli/deploy/blueprints/registry.js +84 -0
- package/out/cli/deploy/layers/identity.js +172 -0
- package/out/cli/deploy-init.js +1182 -347
- package/out/cli/deps.js +106 -2
- package/out/cli/idp-init.js +338 -0
- package/out/cli/index.js +192 -23
- package/out/cli/pack-install-ref.js +15 -0
- package/out/cli/pack-loader.js +45 -3
- package/out/cli/pack-runtime.js +61 -0
- package/out/cli/runtime.js +44 -3
- package/out/cli/serve-hardening.js +139 -3
- package/out/cli/state-sync.js +16 -15
- package/out/cli/utils.js +12 -1
- package/out/engines/base.js +32 -3
- package/out/engines/claude.js +47 -8
- package/out/engines/codex.js +125 -22
- package/out/engines/custom.js +54 -12
- package/out/evidence/sessionShip.js +610 -0
- package/out/extension.js +13 -2
- package/out/gateway/bridge.js +40 -2
- package/out/gateway/client.js +34 -1
- package/out/gateway/installSource.js +68 -0
- package/out/gateway/lifecycle.js +185 -51
- package/out/gateway/outboundPaths.js +4 -1
- package/out/gateway/outboundSend.js +6 -6
- package/out/gateway/secretsPreflight.js +90 -0
- package/out/gateway/service.js +7 -2
- package/out/headless/index.js +40 -9
- package/out/live-e2e/mcp-demo/server.mjs +32 -0
- package/out/live-probe/connector.mjs +8 -0
- package/out/live-probe/env-report.mjs +8 -0
- package/out/mcp.js +115 -67
- package/out/pack/caps.js +271 -0
- package/out/pack/governor.js +309 -0
- package/out/pack/lease-schema.js +270 -0
- package/out/pack/lease.js +640 -0
- package/out/pack/reconcile.js +217 -0
- package/out/pack/signature.js +839 -0
- package/out/remote/handleRemoteChat.js +44 -3
- package/out/remote/remoteTrust.js +277 -0
- package/out/remote/serveClient.js +52 -5
- package/out/runner/host.js +131 -0
- package/out/runner/protocol.js +108 -0
- package/out/runner/remoteEngine.js +130 -0
- package/out/runtime/interfaces/edit-tracker.js +2 -0
- package/out/runtime/vscode/vscode-edit-tracker.js +2 -0
- package/out/sandbox/containerDriver.js +570 -0
- package/out/sandbox/processDriver.js +315 -0
- package/out/sandbox/sandboxRunner.js +191 -0
- package/out/sandbox/support.js +301 -0
- package/out/sandbox/types.js +55 -0
- package/out/scripts/agentide-litellm.sh +67 -5
- package/out/scripts/agentide_litellm_hooks_v1.py +182 -21
- package/out/scripts/analytics/agentide-litellm-otel.config.yaml +94 -118
- package/out/scripts/analytics/setup-analytics.ps1 +37 -13
- package/out/scripts/analytics/setup-analytics.sh +93 -13
- package/out/scripts/analytics/start-analytics.sh +86 -14
- package/out/scripts/gateway/setup-gateway.sh +72 -32
- package/out/shared/models.js +12 -111
- package/out/shared/providers.js +2 -2
- package/out/spotlight/install.js +7 -1
- package/out/store/index.js +41 -10
- package/out/sync.js +3 -1
- package/out/test/analytics.test.js +198 -34
- package/out/test/auth-baseurl.test.js +132 -0
- package/out/test/auth-bootstrapApply.test.js +27 -6
- package/out/test/auth-broker-hardening.test.js +227 -0
- package/out/test/auth-broker.test.js +112 -9
- package/out/test/blueprint-registry.test.js +105 -0
- package/out/test/branding.test.js +13 -0
- package/out/test/caps-governor.test.js +455 -0
- package/out/test/codex-new-file-survival.test.js +119 -0
- package/out/test/configuration-scopes.test.js +131 -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 +311 -0
- package/out/test/deploy-init.test.js +295 -4
- package/out/test/egress-broker.test.js +172 -0
- package/out/test/fleetDisplayUtils.test.js +4 -0
- package/out/test/gateway-client.test.js +28 -0
- package/out/test/gateway-install-source.test.js +111 -0
- package/out/test/gateway-lifecycle.test.js +1 -1
- package/out/test/gateway-outbound.test.js +7 -0
- package/out/test/gateway-secrets-preflight.test.js +127 -0
- package/out/test/gateway-session-routing.test.js +18 -0
- package/out/test/governor-hardening.test.js +217 -0
- package/out/test/headless-sync.test.js +22 -0
- package/out/test/idp-init.test.js +151 -0
- package/out/test/lease-signature.test.js +357 -0
- package/out/test/lease-trust-root.test.js +273 -0
- package/out/test/lease-validation.test.js +169 -0
- package/out/test/lease.test.js +114 -0
- package/out/test/mcp-obo.test.js +52 -27
- package/out/test/mcp.test.js +69 -3
- package/out/test/model-routing.test.js +133 -0
- package/out/test/pack-admission.test.js +216 -0
- package/out/test/pack-digest-parity.test.js +140 -0
- package/out/test/pack-reconcile.test.js +183 -0
- package/out/test/pack-signature-artifact.test.js +103 -0
- package/out/test/pack-signature.test.js +176 -0
- package/out/test/pack-trust-root.test.js +202 -0
- package/out/test/remote-trust.test.js +242 -0
- package/out/test/runner-protocol.test.js +249 -0
- package/out/test/sandbox-driver.test.js +553 -0
- package/out/test/sandbox-hardening.test.js +395 -0
- package/out/test/sandbox-runner.test.js +343 -0
- package/out/test/serve-gateway-turn.test.js +267 -0
- package/out/test/serve-hardening.test.js +125 -6
- package/out/test/serveClient.test.js +38 -5
- package/out/test/sessionShip.test.js +560 -0
- package/out/test/spotlightPublish.test.js +28 -0
- package/out/test/state-sync-peer.test.js +18 -5
- package/out/test/store-install-safe.test.js +69 -0
- package/out/test/tls-bypass.test.js +90 -0
- package/out/test/warrant.test.js +138 -0
- package/out/utils.js +73 -20
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/*---------------------------------------------------------------------------------------------
|
|
3
3
|
* Copyright (c) AgentHippo.ai. All rights reserved.
|
|
4
|
+
* Licensed under the MIT License. See LICENSE.txt in the project root for license information.
|
|
4
5
|
*--------------------------------------------------------------------------------------------*/
|
|
5
6
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
7
|
if (k2 === undefined) k2 = k;
|
|
@@ -37,6 +38,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
37
38
|
})();
|
|
38
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
40
|
exports.ObservableStreamEmitter = exports.SessionEventWriter = void 0;
|
|
41
|
+
exports.getSessionEventWriterStats = getSessionEventWriterStats;
|
|
42
|
+
exports.resetSessionEventWriterStatsForTest = resetSessionEventWriterStatsForTest;
|
|
40
43
|
exports.wrapEmitterWithObservability = wrapEmitterWithObservability;
|
|
41
44
|
/**
|
|
42
45
|
* Session Event Writer & Observable Stream Emitter
|
|
@@ -47,25 +50,51 @@ exports.wrapEmitterWithObservability = wrapEmitterWithObservability;
|
|
|
47
50
|
*
|
|
48
51
|
* Events are written to ~/.agent-hippo/analytics/sessions/<session_id>.jsonl
|
|
49
52
|
* and queryable by DuckDB alongside OTEL traces.
|
|
53
|
+
*
|
|
54
|
+
* PRIVACY: user identifiers are salted hashes and tool I/O is a sha256 + length, unless an
|
|
55
|
+
* operator opts in — see ./identity.ts. These records are shipped wholesale to the evidence
|
|
56
|
+
* server, so whatever lands in this file leaves the machine.
|
|
50
57
|
*/
|
|
51
58
|
const path = __importStar(require("path"));
|
|
52
59
|
const fs_1 = require("fs");
|
|
53
60
|
const branding_1 = require("../branding");
|
|
54
|
-
const credential_context_1 = require("../cli/credential-context");
|
|
55
61
|
const os = __importStar(require("os"));
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
const identity_1 = require("./identity");
|
|
63
|
+
/** Process-wide counters, aggregated across every writer instance. */
|
|
64
|
+
const globalStats = {
|
|
65
|
+
written: 0,
|
|
66
|
+
failed: 0,
|
|
67
|
+
};
|
|
59
68
|
/**
|
|
60
|
-
*
|
|
61
|
-
* One file per session: ~/.agent-hippo/analytics/sessions/<session_id>.jsonl
|
|
69
|
+
* Process-wide evidence-write health, for /health-style output.
|
|
62
70
|
*
|
|
63
|
-
*
|
|
64
|
-
|
|
71
|
+
* `failed > 0` means at least one consequential act produced no record (R-1 completeness).
|
|
72
|
+
*/
|
|
73
|
+
function getSessionEventWriterStats() {
|
|
74
|
+
return { written: globalStats.written, failed: globalStats.failed, lastError: globalStats.lastError };
|
|
75
|
+
}
|
|
76
|
+
/** Reset the process-wide counters. Tests only. */
|
|
77
|
+
function resetSessionEventWriterStatsForTest() {
|
|
78
|
+
globalStats.written = 0;
|
|
79
|
+
globalStats.failed = 0;
|
|
80
|
+
globalStats.lastError = undefined;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Set when evidence is REQUIRED: a failed write then rejects rather than returning, so the turn
|
|
84
|
+
* fails instead of proceeding unrecorded. Off by default — an IDE user should not lose a turn
|
|
85
|
+
* because a disk was full — but a regulated deployment wants the opposite trade.
|
|
65
86
|
*/
|
|
87
|
+
const EVIDENCE_REQUIRED_ENV = 'AGENTHIPPO_EVIDENCE_REQUIRED';
|
|
88
|
+
function evidenceRequired(env = process.env) {
|
|
89
|
+
const v = (env[EVIDENCE_REQUIRED_ENV] ?? '').trim().toLowerCase();
|
|
90
|
+
return v === '1' || v === 'true' || v === 'yes' || v === 'on';
|
|
91
|
+
}
|
|
66
92
|
class SessionEventWriter {
|
|
67
93
|
baseDir;
|
|
68
94
|
ensuredDirs = new Set();
|
|
95
|
+
written = 0;
|
|
96
|
+
failed = 0;
|
|
97
|
+
lastError;
|
|
69
98
|
sanitizeSessionId(sessionId) {
|
|
70
99
|
const trimmed = sessionId.trim();
|
|
71
100
|
if (!trimmed) {
|
|
@@ -79,6 +108,14 @@ class SessionEventWriter {
|
|
|
79
108
|
}
|
|
80
109
|
/**
|
|
81
110
|
* Append a session event to the session's JSONL file.
|
|
111
|
+
*
|
|
112
|
+
* A failed write is a HOLE IN THE RECORD, not a nuisance: the act still happened, and nothing
|
|
113
|
+
* downstream can tell the difference between "no tool ran" and "the tool event never landed".
|
|
114
|
+
* So every failure is logged at error level and counted into /health-visible state. With
|
|
115
|
+
* AGENTHIPPO_EVIDENCE_REQUIRED set it also rejects, so the caller fails the turn rather than
|
|
116
|
+
* completing it unrecorded.
|
|
117
|
+
*
|
|
118
|
+
* This used to swallow every error in an empty catch block — silence on every failure.
|
|
82
119
|
*/
|
|
83
120
|
async append(sessionId, requestId, eventType, payload) {
|
|
84
121
|
try {
|
|
@@ -98,11 +135,27 @@ class SessionEventWriter {
|
|
|
98
135
|
const filePath = path.join(this.baseDir, `${safeSessionId}.jsonl`);
|
|
99
136
|
const line = JSON.stringify(event) + '\n';
|
|
100
137
|
await fs_1.promises.appendFile(filePath, line);
|
|
138
|
+
this.written++;
|
|
139
|
+
globalStats.written++;
|
|
101
140
|
}
|
|
102
|
-
catch {
|
|
103
|
-
|
|
141
|
+
catch (err) {
|
|
142
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
143
|
+
this.failed++;
|
|
144
|
+
globalStats.failed++;
|
|
145
|
+
const record = { at: Date.now(), message, eventType };
|
|
146
|
+
this.lastError = record;
|
|
147
|
+
globalStats.lastError = record;
|
|
148
|
+
console.error(`[evidence] LOST session event ${eventType} (session=${sessionId} request=${requestId}): ` +
|
|
149
|
+
`${message} — ${globalStats.failed} evidence write(s) lost since start`);
|
|
150
|
+
if (evidenceRequired()) {
|
|
151
|
+
throw new Error(`evidence write failed for ${eventType}: ${message}`);
|
|
152
|
+
}
|
|
104
153
|
}
|
|
105
154
|
}
|
|
155
|
+
/** Per-writer evidence-write health. See {@link getSessionEventWriterStats} for the process view. */
|
|
156
|
+
getStats() {
|
|
157
|
+
return { written: this.written, failed: this.failed, lastError: this.lastError };
|
|
158
|
+
}
|
|
106
159
|
/**
|
|
107
160
|
* Get the sessions directory path (for DuckDB queries).
|
|
108
161
|
*/
|
|
@@ -127,13 +180,43 @@ class ObservableStreamEmitter {
|
|
|
127
180
|
writer;
|
|
128
181
|
sessionId;
|
|
129
182
|
requestId;
|
|
183
|
+
stamp;
|
|
130
184
|
missingCallIdCounter = 0;
|
|
131
185
|
pendingSyntheticCallIds = new Map();
|
|
132
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Salted pseudonym of the acting user — the warrant, in the form the evidence index wants
|
|
188
|
+
* (PLAN/compliance.md §2 "salted identity hashes"). Raw only under
|
|
189
|
+
* AGENTHIPPO_EVIDENCE_RAW_IDS=1. Derived in the constructor; the raw id is never a field.
|
|
190
|
+
*/
|
|
191
|
+
actorId;
|
|
192
|
+
constructor(inner, writer, sessionId, requestId,
|
|
193
|
+
/**
|
|
194
|
+
* Acting user for the warrant audit trail on tool events. Captured once at
|
|
195
|
+
* wrap time (still inside the request's credential scope) and stored on this
|
|
196
|
+
* per-turn emitter — never re-read from ambient AsyncLocalStorage at emit
|
|
197
|
+
* time. Custom-engine persistent workers deliver tool events on a detached
|
|
198
|
+
* parent IPC callback whose ALS can stick to the spawn request; constructor
|
|
199
|
+
* capture keeps attribution correct for every engine. Undefined outside a
|
|
200
|
+
* serve credential scope (IDE-local / CLI without runWithCredential).
|
|
201
|
+
*
|
|
202
|
+
* NOT a field: it is hashed into `actorId` in the constructor and the raw value is then
|
|
203
|
+
* unreachable from any emit path.
|
|
204
|
+
*/
|
|
205
|
+
userId,
|
|
206
|
+
/**
|
|
207
|
+
* Provenance stamp merged into turn_start (policy_version, pack_digest, engine_version,
|
|
208
|
+
* tier) — which rules and which artifacts governed this turn, recorded at the moment of
|
|
209
|
+
* the act rather than reconstructed later (PLAN/compliance.md §3.1).
|
|
210
|
+
*/
|
|
211
|
+
stamp) {
|
|
133
212
|
this.inner = inner;
|
|
134
213
|
this.writer = writer;
|
|
135
214
|
this.sessionId = sessionId;
|
|
136
215
|
this.requestId = requestId;
|
|
216
|
+
this.stamp = stamp;
|
|
217
|
+
// Hash once at construction: the raw id is never stored on this object, so no later edit
|
|
218
|
+
// to an emit path can leak it by reaching for `this.userId`.
|
|
219
|
+
this.actorId = (0, identity_1.hashIdentity)(userId);
|
|
137
220
|
}
|
|
138
221
|
makeCallIdKey(toolName) {
|
|
139
222
|
return `${this.sessionId}:${this.requestId}:${toolName}`;
|
|
@@ -174,11 +257,12 @@ class ObservableStreamEmitter {
|
|
|
174
257
|
await this.writer.append(this.sessionId, this.requestId, 'tool_start', {
|
|
175
258
|
toolName,
|
|
176
259
|
callId: loggedCallId,
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
260
|
+
// Tool arguments are content — paths, queries, credentials people paste into prompts.
|
|
261
|
+
// Digest by default (input_sha256 + input_length); `inputPreview` returns only under
|
|
262
|
+
// AGENTHIPPO_EVIDENCE_CONTENT_CAPTURE=1.
|
|
263
|
+
...(0, identity_1.describeContent)('input', input),
|
|
264
|
+
// The warrant: who this tool call ran as (captured at wrap; see constructor).
|
|
265
|
+
userId: this.actorId,
|
|
182
266
|
});
|
|
183
267
|
return this.inner.toolStart(toolName, input, toolCallId, metadata);
|
|
184
268
|
}
|
|
@@ -190,9 +274,10 @@ class ObservableStreamEmitter {
|
|
|
190
274
|
toolName,
|
|
191
275
|
callId: loggedCallId,
|
|
192
276
|
isError: isError ?? false,
|
|
193
|
-
//
|
|
194
|
-
resultPreview
|
|
195
|
-
|
|
277
|
+
// Tool output is content — file contents, command output, customer data. Digest by
|
|
278
|
+
// default; `resultPreview` returns only under AGENTHIPPO_EVIDENCE_CONTENT_CAPTURE=1.
|
|
279
|
+
...(0, identity_1.describeContent)('result', result),
|
|
280
|
+
userId: this.actorId,
|
|
196
281
|
});
|
|
197
282
|
return this.inner.toolEnd(toolName, toolCallId, result, isError, metadata);
|
|
198
283
|
}
|
|
@@ -205,6 +290,7 @@ class ObservableStreamEmitter {
|
|
|
205
290
|
async error(message) {
|
|
206
291
|
await this.writer.append(this.sessionId, this.requestId, 'error', {
|
|
207
292
|
message,
|
|
293
|
+
userId: this.actorId,
|
|
208
294
|
});
|
|
209
295
|
return this.inner.error(message);
|
|
210
296
|
}
|
|
@@ -215,7 +301,7 @@ class ObservableStreamEmitter {
|
|
|
215
301
|
* Emit a turn_start event. Call at the beginning of stream().
|
|
216
302
|
*/
|
|
217
303
|
async emitTurnStart() {
|
|
218
|
-
await this.writer.append(this.sessionId, this.requestId, 'turn_start', {});
|
|
304
|
+
await this.writer.append(this.sessionId, this.requestId, 'turn_start', { userId: this.actorId, ...this.stamp });
|
|
219
305
|
}
|
|
220
306
|
/**
|
|
221
307
|
* Emit a turn_end event. Call at the end of stream().
|
|
@@ -223,6 +309,7 @@ class ObservableStreamEmitter {
|
|
|
223
309
|
async emitTurnEnd(success) {
|
|
224
310
|
await this.writer.append(this.sessionId, this.requestId, 'turn_end', {
|
|
225
311
|
success,
|
|
312
|
+
userId: this.actorId,
|
|
226
313
|
});
|
|
227
314
|
}
|
|
228
315
|
}
|
|
@@ -231,6 +318,6 @@ exports.ObservableStreamEmitter = ObservableStreamEmitter;
|
|
|
231
318
|
* Create an ObservableStreamEmitter that wraps the given emitter.
|
|
232
319
|
* Returns both the wrapped emitter and emit helpers for turn boundaries.
|
|
233
320
|
*/
|
|
234
|
-
function wrapEmitterWithObservability(inner, writer, sessionId, requestId) {
|
|
235
|
-
return new ObservableStreamEmitter(inner, writer, sessionId, requestId);
|
|
321
|
+
function wrapEmitterWithObservability(inner, writer, sessionId, requestId, userId, stamp) {
|
|
322
|
+
return new ObservableStreamEmitter(inner, writer, sessionId, requestId, userId, stamp);
|
|
236
323
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) AgentHippo.ai. All rights reserved.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.assertSafeAuthBaseUrl = void 0;
|
|
7
|
+
exports.pickTrustedAuthBaseUrl = pickTrustedAuthBaseUrl;
|
|
8
|
+
exports.resolveAuthBrokerBaseUrl = resolveAuthBrokerBaseUrl;
|
|
9
|
+
/**
|
|
10
|
+
* Where `agentide.auth.baseUrl` is allowed to come from.
|
|
11
|
+
*
|
|
12
|
+
* The setting is declared `"scope": "application"` in package.json, which is what actually stops a
|
|
13
|
+
* repo's `.vscode/settings.json` from setting it. This module is the defence in depth behind that
|
|
14
|
+
* declaration: it re-derives the value from `inspect()` and takes ONLY the user/application/default
|
|
15
|
+
* layers, so even if the contribution scope is ever loosened by accident (or the value arrives
|
|
16
|
+
* through a remote/folder layer this extension does not expect), a workspace-supplied endpoint is
|
|
17
|
+
* dropped rather than trusted.
|
|
18
|
+
*
|
|
19
|
+
* Why this matters concretely: the resolved URL receives the PKCE authorization code, then
|
|
20
|
+
* `Authorization: Bearer <AgentHippo access token>` on `/api/ide/bootstrap`, and the response
|
|
21
|
+
* configures team store settings plus `agentide.AgentHippoModelGateway.url` (application-scoped).
|
|
22
|
+
* The access token is also synced to the `agenthippo` model provider. A workspace that could
|
|
23
|
+
* steer the auth endpoint would capture the token AND every subsequent prompt.
|
|
24
|
+
*/
|
|
25
|
+
const brokerClient_js_1 = require("./brokerClient.js");
|
|
26
|
+
Object.defineProperty(exports, "assertSafeAuthBaseUrl", { enumerable: true, get: function () { return brokerClient_js_1.assertSafeAuthBaseUrl; } });
|
|
27
|
+
/**
|
|
28
|
+
* Pick the auth base URL from the trusted layers only.
|
|
29
|
+
*
|
|
30
|
+
* Precedence matches VS Code's own (most specific trusted layer wins): remote user → local user →
|
|
31
|
+
* user → default. `workspaceValue` / `workspaceFolderValue` are never consulted for the result;
|
|
32
|
+
* they are only reported so the caller can warn.
|
|
33
|
+
*/
|
|
34
|
+
function pickTrustedAuthBaseUrl(inspect) {
|
|
35
|
+
if (!inspect) {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
const ignored = inspect.workspaceFolderValue?.trim() || inspect.workspaceValue?.trim() || undefined;
|
|
39
|
+
const trusted = inspect.globalRemoteValue?.trim() ||
|
|
40
|
+
inspect.globalLocalValue?.trim() ||
|
|
41
|
+
inspect.globalValue?.trim() ||
|
|
42
|
+
inspect.defaultValue?.trim() ||
|
|
43
|
+
undefined;
|
|
44
|
+
// A workspace value that merely repeats the trusted value is not an override worth warning about.
|
|
45
|
+
const ignoredWorkspaceValue = ignored && ignored !== trusted ? ignored : undefined;
|
|
46
|
+
return { value: trusted, ...(ignoredWorkspaceValue ? { ignoredWorkspaceValue } : {}) };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the auth broker base URL from configuration, dropping workspace-scoped values and
|
|
50
|
+
* enforcing the https/loopback requirement. Throws (with an actionable message) on a bad URL.
|
|
51
|
+
*/
|
|
52
|
+
function resolveAuthBrokerBaseUrl(config, warn) {
|
|
53
|
+
const picked = pickTrustedAuthBaseUrl(config?.inspect('auth.baseUrl'));
|
|
54
|
+
if (picked.ignoredWorkspaceValue) {
|
|
55
|
+
warn?.(`[AgentIDE] Ignoring workspace-provided "agentide.auth.baseUrl" (${picked.ignoredWorkspaceValue}). ` +
|
|
56
|
+
`This endpoint receives your AgentHippo access token, so it is only honoured from user/application settings.`);
|
|
57
|
+
}
|
|
58
|
+
return (0, brokerClient_js_1.getAuthBrokerBaseUrl)(picked.value);
|
|
59
|
+
}
|
|
@@ -19,7 +19,6 @@ async function applyIdeBootstrap(bootstrap, target) {
|
|
|
19
19
|
const skipped = [];
|
|
20
20
|
const teamManaged = bootstrap.policy.teamManagedSettings ?? [];
|
|
21
21
|
const storeConfig = target.getConfiguration('agentide');
|
|
22
|
-
const litellmConfig = target.getConfiguration('agentide.providers.litellm');
|
|
23
22
|
if (shouldApplySetting(target.globalStateGet(exports.TEAM_MANAGED_STORE_KEY), teamManaged, 'store')) {
|
|
24
23
|
await storeConfig.update('storeRepo', bootstrap.store.repo, target.ConfigurationTarget.Global);
|
|
25
24
|
await storeConfig.update('store.indexUrl', bootstrap.store.indexUrl, target.ConfigurationTarget.Global);
|
|
@@ -29,22 +28,19 @@ async function applyIdeBootstrap(bootstrap, target) {
|
|
|
29
28
|
else {
|
|
30
29
|
skipped.push('store');
|
|
31
30
|
}
|
|
31
|
+
// Routing configures the `agenthippo` provider slot ONLY. The `litellm` slot stays user-owned
|
|
32
|
+
// (a developer's local Spotlight proxy) — sign-in must never overwrite it. No credential is
|
|
33
|
+
// written here: the account JWT is synced to the provider by the auth provider's timer, and
|
|
34
|
+
// the bootstrap carries no routing key at all.
|
|
32
35
|
if (shouldApplySetting(target.globalStateGet(exports.TEAM_MANAGED_ROUTING_KEY), teamManaged, 'routing')) {
|
|
33
|
-
await
|
|
34
|
-
if (target.setParticipantApiKey) {
|
|
35
|
-
await target.setParticipantApiKey({
|
|
36
|
-
provider: 'litellm',
|
|
37
|
-
apiKey: bootstrap.routing.litellmApiKey,
|
|
38
|
-
baseUrl: bootstrap.routing.litellmBaseUrl,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
36
|
+
await storeConfig.update('AgentHippoModelGateway.url', bootstrap.routing.inferenceGatewayUrl, target.ConfigurationTarget.Global);
|
|
41
37
|
await target.globalStateUpdate(exports.TEAM_MANAGED_ROUTING_KEY, teamManaged.includes('routing'));
|
|
42
38
|
applied.push('routing');
|
|
43
39
|
}
|
|
44
40
|
else {
|
|
45
41
|
skipped.push('routing');
|
|
46
42
|
}
|
|
47
|
-
await target.globalStateUpdate(exports.TEAM_ID_KEY, bootstrap.team
|
|
43
|
+
await target.globalStateUpdate(exports.TEAM_ID_KEY, bootstrap.team?.id);
|
|
48
44
|
await target.globalStateUpdate(exports.TEAM_LAST_BOOTSTRAP_KEY, new Date().toISOString());
|
|
49
45
|
await target.globalStateUpdate(exports.TEAM_BOOTSTRAP_CACHE_KEY, bootstrap);
|
|
50
46
|
return { applied, skipped };
|
|
@@ -59,6 +55,9 @@ async function revertTeamManagedBootstrap(target) {
|
|
|
59
55
|
await storeConfig.update('store.indexUrl', 'https://agenthippoai.github.io/agenthippo-store', target.ConfigurationTarget.Global);
|
|
60
56
|
}
|
|
61
57
|
if (routingManaged) {
|
|
58
|
+
await storeConfig.update('AgentHippoModelGateway.url', undefined, target.ConfigurationTarget.Global);
|
|
59
|
+
// Clean up state written by pre-separation installs, where team routing overwrote the
|
|
60
|
+
// user-owned litellm slot with the team base URL and a shared key.
|
|
62
61
|
await litellmConfig.update('baseUrl', '', target.ConfigurationTarget.Global);
|
|
63
62
|
if (target.setParticipantApiKey) {
|
|
64
63
|
await target.setParticipantApiKey({ provider: 'litellm', apiKey: '', baseUrl: '' });
|
package/out/auth/brokerClient.js
CHANGED
|
@@ -3,21 +3,66 @@
|
|
|
3
3
|
* Copyright (c) AgentHippo.ai. All rights reserved.
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_AUTH_BROKER_BASE_URL = void 0;
|
|
7
|
+
exports.isLoopbackAuthHost = isLoopbackAuthHost;
|
|
8
|
+
exports.assertSafeAuthBaseUrl = assertSafeAuthBaseUrl;
|
|
6
9
|
exports.getAuthBrokerBaseUrl = getAuthBrokerBaseUrl;
|
|
7
10
|
exports.devLoginForTests = devLoginForTests;
|
|
8
11
|
exports.exchangeAuthorizationCode = exchangeAuthorizationCode;
|
|
9
12
|
exports.refreshAccessToken = refreshAccessToken;
|
|
10
|
-
exports.fetchBootstrapRefresh = fetchBootstrapRefresh;
|
|
11
13
|
exports.fetchBootstrap = fetchBootstrap;
|
|
12
14
|
exports.fetchMe = fetchMe;
|
|
13
15
|
exports.buildAuthorizeUrl = buildAuthorizeUrl;
|
|
14
16
|
exports.completeDevPkceLogin = completeDevPkceLogin;
|
|
15
17
|
const types_js_1 = require("./types.js");
|
|
16
18
|
const pkce_js_1 = require("./pkce.js");
|
|
19
|
+
exports.DEFAULT_AUTH_BROKER_BASE_URL = 'https://auth.agenthippo.ai';
|
|
20
|
+
/** Hostnames for which plain `http://` is accepted — an operator running a broker locally. */
|
|
21
|
+
const LOOPBACK_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);
|
|
22
|
+
function isLoopbackAuthHost(hostname) {
|
|
23
|
+
return LOOPBACK_HOSTNAMES.has(hostname.toLowerCase());
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Refuse an auth base URL that cannot safely carry a bearer token.
|
|
27
|
+
*
|
|
28
|
+
* Every request built from this value carries either the PKCE authorization code or the user's
|
|
29
|
+
* AgentHippo access token (`/oauth/token`, `/api/ide/bootstrap`, `/api/ide/me`), and the bootstrap
|
|
30
|
+
* response is written straight into the user's model-routing settings. A cleartext or non-http(s)
|
|
31
|
+
* endpoint therefore leaks the credential on the wire; anything but https is refused unless the
|
|
32
|
+
* host is loopback, which is the local-development case and never crosses a network.
|
|
33
|
+
*
|
|
34
|
+
* Returns the normalized (trailing-slash-stripped) URL, or throws with an actionable message.
|
|
35
|
+
*/
|
|
36
|
+
function assertSafeAuthBaseUrl(value) {
|
|
37
|
+
const trimmed = value.trim();
|
|
38
|
+
let url;
|
|
39
|
+
try {
|
|
40
|
+
url = new URL(trimmed);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
throw new Error(`AgentHippo auth base URL "${trimmed}" is not a valid URL. ` +
|
|
44
|
+
`Fix "agentide.auth.baseUrl" (or AGENTHIPPO_AUTH_BASE_URL), or clear it to use ${exports.DEFAULT_AUTH_BROKER_BASE_URL}.`);
|
|
45
|
+
}
|
|
46
|
+
if (url.protocol === 'https:') {
|
|
47
|
+
return trimmed.replace(/\/+$/, '');
|
|
48
|
+
}
|
|
49
|
+
if (url.protocol === 'http:' && isLoopbackAuthHost(url.hostname)) {
|
|
50
|
+
return trimmed.replace(/\/+$/, '');
|
|
51
|
+
}
|
|
52
|
+
throw new Error(`Refusing to use AgentHippo auth base URL "${trimmed}": it must be https:// ` +
|
|
53
|
+
`(plain http:// is accepted only for loopback hosts such as http://127.0.0.1:9780). ` +
|
|
54
|
+
`Your AgentHippo access token is sent to this host, so it is never sent over cleartext. ` +
|
|
55
|
+
`Fix "agentide.auth.baseUrl" (or AGENTHIPPO_AUTH_BASE_URL), or clear it to use ${exports.DEFAULT_AUTH_BROKER_BASE_URL}.`);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the auth broker base URL. `configured` must already have been filtered for scope by the
|
|
59
|
+
* caller (see `auth/authBaseUrl.ts` — a workspace-scoped value is dropped before it gets here);
|
|
60
|
+
* this function is the second gate and enforces the transport requirement.
|
|
61
|
+
*/
|
|
17
62
|
function getAuthBrokerBaseUrl(configured) {
|
|
18
63
|
const fromEnv = process.env.AGENTHIPPO_AUTH_BASE_URL?.trim();
|
|
19
|
-
const value = configured?.trim() || fromEnv ||
|
|
20
|
-
return value
|
|
64
|
+
const value = configured?.trim() || fromEnv || exports.DEFAULT_AUTH_BROKER_BASE_URL;
|
|
65
|
+
return assertSafeAuthBaseUrl(value);
|
|
21
66
|
}
|
|
22
67
|
async function devLoginForTests(baseUrl, email, redirectUri, codeChallenge) {
|
|
23
68
|
const res = await fetch(`${baseUrl}/oauth/dev/login`, {
|
|
@@ -73,17 +118,6 @@ async function refreshAccessToken(baseUrl, refreshToken) {
|
|
|
73
118
|
}
|
|
74
119
|
return res.json();
|
|
75
120
|
}
|
|
76
|
-
async function fetchBootstrapRefresh(baseUrl, accessToken) {
|
|
77
|
-
const res = await fetch(`${baseUrl}/api/ide/bootstrap/refresh`, {
|
|
78
|
-
method: 'POST',
|
|
79
|
-
headers: { Authorization: `Bearer ${accessToken}` },
|
|
80
|
-
});
|
|
81
|
-
if (!res.ok) {
|
|
82
|
-
const text = await res.text();
|
|
83
|
-
throw new Error(`bootstrap refresh failed: ${res.status} ${text}`.trim());
|
|
84
|
-
}
|
|
85
|
-
return res.json();
|
|
86
|
-
}
|
|
87
121
|
async function fetchBootstrap(baseUrl, accessToken) {
|
|
88
122
|
const res = await fetch(`${baseUrl}/api/ide/bootstrap`, {
|
|
89
123
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
@@ -118,7 +152,7 @@ function buildAuthorizeUrl(baseUrl, redirectUri, codeChallenge, state, loginHint
|
|
|
118
152
|
}
|
|
119
153
|
return url.toString();
|
|
120
154
|
}
|
|
121
|
-
async function completeDevPkceLogin(baseUrl, email, redirectUri = '
|
|
155
|
+
async function completeDevPkceLogin(baseUrl, email, redirectUri = 'http://127.0.0.1:1/callback') {
|
|
122
156
|
const verifier = (0, pkce_js_1.generateCodeVerifier)();
|
|
123
157
|
const challenge = (0, pkce_js_1.codeChallengeFromVerifier)(verifier);
|
|
124
158
|
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
|
@@ -41,14 +41,14 @@ exports.registerAgentHippoAuth = registerAgentHippoAuth;
|
|
|
41
41
|
exports.refreshBootstrapIfSignedIn = refreshBootstrapIfSignedIn;
|
|
42
42
|
exports.startAgentHippoTokenRefreshTimer = startAgentHippoTokenRefreshTimer;
|
|
43
43
|
const vscode = __importStar(require("vscode"));
|
|
44
|
-
const
|
|
44
|
+
const authBaseUrl_js_1 = require("./authBaseUrl.js");
|
|
45
45
|
const bootstrapApply_js_1 = require("./bootstrapApply.js");
|
|
46
46
|
const types_js_1 = require("./types.js");
|
|
47
47
|
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
|
}
|
|
@@ -111,7 +111,7 @@ class AgentHippoAuthProvider {
|
|
|
111
111
|
const stored = await (0, sessionStore_js_1.getStoredAuthSession)(this.context.secrets);
|
|
112
112
|
const removedSession = stored ? (0, sessionStore_js_1.storedSessionToVsCode)(stored) : undefined;
|
|
113
113
|
if (stored) {
|
|
114
|
-
const baseUrl = (0,
|
|
114
|
+
const baseUrl = (0, authBaseUrl_js_1.resolveAuthBrokerBaseUrl)(vscode.workspace.getConfiguration('agentide'), (m) => console.warn(m));
|
|
115
115
|
try {
|
|
116
116
|
await fetch(`${baseUrl}/api/ide/signout`, {
|
|
117
117
|
method: 'POST',
|
|
@@ -184,20 +184,17 @@ async function refreshBootstrapIfSignedIn(context) {
|
|
|
184
184
|
void syncAgentHippoModelGatewayToken(token.accessToken);
|
|
185
185
|
}
|
|
186
186
|
/**
|
|
187
|
-
* Refresh cadence for the model-gateway JWT
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* onDidChangeKeys every tick when nothing changed.
|
|
187
|
+
* Refresh cadence for the model-gateway JWT. The access token TTL is ~1h;
|
|
188
|
+
* getAgentHippoAccessToken refreshes it inside a 5-min pre-expiry window, so a 60s tick always
|
|
189
|
+
* lands inside that window with buffer and re-syncs the agenthippo provider key before the old
|
|
190
|
+
* JWT expires (fixes stale-key 401s on long sessions). The sync is diff-gated so we don't churn
|
|
191
|
+
* SecretStorage / onDidChangeKeys every tick when nothing changed. There is no routing-key
|
|
192
|
+
* refresh: the bootstrap carries no routing credential — the JWT is the credential.
|
|
194
193
|
*/
|
|
195
194
|
const TOKEN_REFRESH_INTERVAL_MS = 60_000;
|
|
196
|
-
const ROUTING_REFRESH_EVERY_N_TICKS = 5;
|
|
197
195
|
function startAgentHippoTokenRefreshTimer(context) {
|
|
198
196
|
let lastSyncedAccessToken = '';
|
|
199
197
|
let running = false;
|
|
200
|
-
let tick = 0;
|
|
201
198
|
const handle = setInterval(() => {
|
|
202
199
|
// Guard against overlapping ticks: a stalled fetch shouldn't let the next tick stack up.
|
|
203
200
|
if (running) {
|
|
@@ -219,16 +216,6 @@ function startAgentHippoTokenRefreshTimer(context) {
|
|
|
219
216
|
lastSyncedAccessToken = token.accessToken;
|
|
220
217
|
}
|
|
221
218
|
}
|
|
222
|
-
// Routing key rotation is rare; refresh infrequently and let the helper skip the
|
|
223
|
-
// settings write when the key hasn't changed.
|
|
224
|
-
if (++tick % ROUTING_REFRESH_EVERY_N_TICKS === 0) {
|
|
225
|
-
try {
|
|
226
|
-
await (0, sessionStore_js_1.refreshRoutingCredentialsForContext)(context, token.accessToken);
|
|
227
|
-
}
|
|
228
|
-
catch {
|
|
229
|
-
// best-effort routing refresh; next cycle retries
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
219
|
}
|
|
233
220
|
finally {
|
|
234
221
|
running = false;
|