jeopi-cli 16.6.8 → 16.6.11
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/CHANGELOG.md +23 -0
- package/dist/cli.js +4071 -4075
- package/dist/types/messenger/live-session.d.ts +28 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/session-teardown.d.ts +13 -8
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/web/search/index.d.ts +2 -1
- package/dist/types/web/search/providers/base.d.ts +6 -1
- package/dist/types/web/search/providers/gemini.d.ts +4 -3
- package/package.json +12 -12
- package/src/config/model-registry.ts +50 -17
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/messenger/live-session.ts +321 -0
- package/src/modes/interactive-mode.ts +31 -7
- package/src/modes/session-teardown.test.ts +61 -0
- package/src/modes/session-teardown.ts +20 -8
- package/src/modes/types.ts +2 -0
- package/src/prompts/advisor/system.md +1 -2
- package/src/prompts/agents/tester.md +1 -2
- package/src/prompts/system/system-prompt.md +1 -2
- package/src/sdk.ts +12 -5
- package/src/slash-commands/builtin-registry.ts +104 -0
- package/src/web/search/index.ts +13 -5
- package/src/web/search/providers/base.ts +14 -1
- package/src/web/search/providers/gemini.ts +28 -9
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
MarketplaceManager,
|
|
25
25
|
} from "../extensibility/plugins/marketplace";
|
|
26
26
|
import { resolveMemoryBackend } from "../memory-backend";
|
|
27
|
+
import { loadAndCreateLiveDiscordBridge } from "../messenger/live-session";
|
|
27
28
|
import { describeLoopLimitRuntime } from "../modes/loop-limit";
|
|
28
29
|
import { theme } from "../modes/theme/theme";
|
|
29
30
|
import type { InteractiveModeContext } from "../modes/types";
|
|
@@ -66,6 +67,13 @@ import type {
|
|
|
66
67
|
|
|
67
68
|
export type { BuiltinSlashCommand, SubcommandDef } from "./types";
|
|
68
69
|
|
|
70
|
+
interface LiveDiscordStart {
|
|
71
|
+
cancelled: boolean;
|
|
72
|
+
promise: Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const liveDiscordStarts = new WeakMap<InteractiveModeContext, LiveDiscordStart>();
|
|
76
|
+
|
|
69
77
|
/** TUI-specific runtime accepted by `executeBuiltinSlashCommand`. */
|
|
70
78
|
export type BuiltinSlashCommandRuntime = TuiSlashCommandRuntime;
|
|
71
79
|
|
|
@@ -650,6 +658,102 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
650
658
|
runtime.ctx.editor.setText("");
|
|
651
659
|
},
|
|
652
660
|
},
|
|
661
|
+
{
|
|
662
|
+
name: "discord",
|
|
663
|
+
description: "Opt in to this session from an authorized Discord channel",
|
|
664
|
+
inlineHint: "[live|stop|status] [configPath] [--allow-full-control]",
|
|
665
|
+
subcommands: [
|
|
666
|
+
{ name: "live", description: "Start the live Discord bridge with a private config file" },
|
|
667
|
+
{ name: "status", description: "Show live Discord bridge status" },
|
|
668
|
+
{ name: "stop", description: "Stop the live Discord bridge" },
|
|
669
|
+
],
|
|
670
|
+
allowArgs: true,
|
|
671
|
+
getTuiAutocompleteDescription: runtime =>
|
|
672
|
+
runtime.ctx.liveDiscordBridge?.active ? "Discord live bridge: active" : "Discord live bridge: off",
|
|
673
|
+
handleTui: async (command, runtime) => {
|
|
674
|
+
const ctx = runtime.ctx;
|
|
675
|
+
ctx.editor.setText("");
|
|
676
|
+
const { verb, rest } = parseSubcommand(command.args.trim());
|
|
677
|
+
if (verb === "status") {
|
|
678
|
+
if (!ctx.liveDiscordBridge?.active) {
|
|
679
|
+
ctx.showStatus("Discord live bridge is off");
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
const approvalMode = String(ctx.session.settings.get("tools.approvalMode"));
|
|
683
|
+
const activeTools = ctx.session.getActiveToolNames();
|
|
684
|
+
ctx.showStatus(
|
|
685
|
+
`Discord live bridge is active with full host control acknowledged. Approval mode: ${approvalMode}; active tools: ${activeTools.length > 0 ? activeTools.join(", ") : "none"}.`,
|
|
686
|
+
);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
if (verb === "stop") {
|
|
690
|
+
const bridge = ctx.liveDiscordBridge;
|
|
691
|
+
const start = liveDiscordStarts.get(ctx);
|
|
692
|
+
if (!bridge && start) {
|
|
693
|
+
start.cancelled = true;
|
|
694
|
+
ctx.showStatus("Discord live bridge startup cancellation requested");
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (!bridge) {
|
|
698
|
+
ctx.showStatus("Discord live bridge is not active");
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
ctx.liveDiscordBridge = undefined;
|
|
702
|
+
try {
|
|
703
|
+
await bridge.close();
|
|
704
|
+
ctx.showStatus("Discord live bridge stopped; access revoked");
|
|
705
|
+
} catch (error) {
|
|
706
|
+
ctx.showError(`Failed to stop Discord live bridge: ${errorMessage(error)}`);
|
|
707
|
+
}
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const acknowledgment = /^(.+?)\s+--allow-full-control$/.exec(rest);
|
|
711
|
+
if (verb !== "live" || !acknowledgment) {
|
|
712
|
+
ctx.showError("Usage: /discord live <private messenger.json> --allow-full-control");
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (ctx.liveDiscordBridge?.active || liveDiscordStarts.has(ctx)) {
|
|
716
|
+
ctx.showError("Discord live bridge is already active or starting (/discord stop first)");
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
let resolveStart!: () => void;
|
|
720
|
+
const startPromise = new Promise<void>(resolve => {
|
|
721
|
+
resolveStart = resolve;
|
|
722
|
+
});
|
|
723
|
+
const startState: LiveDiscordStart = { cancelled: false, promise: startPromise };
|
|
724
|
+
liveDiscordStarts.set(ctx, startState);
|
|
725
|
+
try {
|
|
726
|
+
const configPath = resolveToCwd(acknowledgment[1]!, ctx.sessionManager.getCwd());
|
|
727
|
+
const bridge = await loadAndCreateLiveDiscordBridge(ctx, configPath, {}, { acknowledgeFullControl: true });
|
|
728
|
+
if (startState.cancelled || ctx.shutdownRequested || ctx.isShuttingDown) {
|
|
729
|
+
await bridge.close().catch(() => {});
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
if (ctx.liveDiscordBridge?.active) {
|
|
733
|
+
await bridge.close().catch(() => {});
|
|
734
|
+
ctx.showError("Discord live bridge is already active (/discord stop first)");
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
ctx.liveDiscordBridge = bridge;
|
|
738
|
+
void bridge.run().catch(error => {
|
|
739
|
+
if (ctx.liveDiscordBridge !== bridge || ctx.isShuttingDown) return;
|
|
740
|
+
ctx.liveDiscordBridge = undefined;
|
|
741
|
+
ctx.showError(`Discord live bridge stopped: ${errorMessage(error)}`);
|
|
742
|
+
});
|
|
743
|
+
const approvalMode = String(ctx.session.settings.get("tools.approvalMode"));
|
|
744
|
+
const activeTools = ctx.session.getActiveToolNames();
|
|
745
|
+
ctx.showStatus(
|
|
746
|
+
`Discord live bridge active with full host control acknowledged. Approval mode: ${approvalMode}; active tools: ${activeTools.length > 0 ? activeTools.join(", ") : "none"}. /discord stop revokes access.`,
|
|
747
|
+
);
|
|
748
|
+
} catch (error) {
|
|
749
|
+
ctx.showError(`Failed to start Discord live bridge: ${errorMessage(error)}`);
|
|
750
|
+
} finally {
|
|
751
|
+
resolveStart();
|
|
752
|
+
if (liveDiscordStarts.get(ctx)?.promise === startPromise) liveDiscordStarts.delete(ctx);
|
|
753
|
+
}
|
|
754
|
+
},
|
|
755
|
+
},
|
|
756
|
+
|
|
653
757
|
{
|
|
654
758
|
name: "collab",
|
|
655
759
|
description: "Share this session live via a relay",
|
package/src/web/search/index.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { type } from "arktype";
|
|
9
9
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "jeopi-agent-core";
|
|
10
|
-
import type { AuthStorage } from "jeopi-ai";
|
|
10
|
+
import type { Api, AuthStorage, Model } from "jeopi-ai";
|
|
11
11
|
import { prompt } from "jeopi-utils";
|
|
12
12
|
import { settings } from "../../config/settings";
|
|
13
13
|
import type { CustomTool, CustomToolContext, RenderResultOptions } from "../../extensibility/custom-tools/types";
|
|
@@ -117,6 +117,7 @@ function hasRenderableSearchContent(response: SearchResponse): boolean {
|
|
|
117
117
|
|
|
118
118
|
interface ExecuteSearchOptions {
|
|
119
119
|
authStorage: AuthStorage;
|
|
120
|
+
model?: Model<Api>;
|
|
120
121
|
sessionId?: string;
|
|
121
122
|
signal?: AbortSignal;
|
|
122
123
|
}
|
|
@@ -127,7 +128,7 @@ async function executeSearch(
|
|
|
127
128
|
params: SearchQueryParams,
|
|
128
129
|
options: ExecuteSearchOptions,
|
|
129
130
|
): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails }> {
|
|
130
|
-
const { authStorage, sessionId, signal } = options;
|
|
131
|
+
const { authStorage, model, sessionId, signal } = options;
|
|
131
132
|
const explicitProvider = params.provider;
|
|
132
133
|
let providers: SearchProvider[];
|
|
133
134
|
if (explicitProvider && explicitProvider !== "auto") {
|
|
@@ -174,6 +175,7 @@ async function executeSearch(
|
|
|
174
175
|
numSearchResults: params.num_search_results,
|
|
175
176
|
temperature: params.temperature,
|
|
176
177
|
signal,
|
|
178
|
+
model,
|
|
177
179
|
authStorage,
|
|
178
180
|
sessionId,
|
|
179
181
|
antigravityEndpointMode,
|
|
@@ -222,7 +224,7 @@ async function executeSearch(
|
|
|
222
224
|
*/
|
|
223
225
|
export async function runSearchQuery(
|
|
224
226
|
params: SearchQueryParams,
|
|
225
|
-
options: { authStorage?: AuthStorage; sessionId?: string; signal?: AbortSignal } = {},
|
|
227
|
+
options: { authStorage?: AuthStorage; model?: Model<Api>; sessionId?: string; signal?: AbortSignal } = {},
|
|
226
228
|
): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails }> {
|
|
227
229
|
const createdAuthStorage = options.authStorage ? undefined : await discoverAuthStorage();
|
|
228
230
|
const authStorage = options.authStorage ?? createdAuthStorage;
|
|
@@ -232,6 +234,7 @@ export async function runSearchQuery(
|
|
|
232
234
|
try {
|
|
233
235
|
return await executeSearch("cli-web-search", params, {
|
|
234
236
|
authStorage,
|
|
237
|
+
model: options.model,
|
|
235
238
|
sessionId: options.sessionId,
|
|
236
239
|
signal: options.signal,
|
|
237
240
|
});
|
|
@@ -271,7 +274,12 @@ export class WebSearchTool implements AgentTool<typeof webSearchSchema, SearchRe
|
|
|
271
274
|
): Promise<AgentToolResult<SearchRenderDetails>> {
|
|
272
275
|
const authStorage = this.#session.authStorage ?? (await discoverAuthStorage());
|
|
273
276
|
const sessionId = this.#session.getSessionId?.() ?? undefined;
|
|
274
|
-
return executeSearch(_toolCallId, params, {
|
|
277
|
+
return executeSearch(_toolCallId, params, {
|
|
278
|
+
authStorage,
|
|
279
|
+
model: this.#session.getActiveModel?.(),
|
|
280
|
+
sessionId,
|
|
281
|
+
signal,
|
|
282
|
+
});
|
|
275
283
|
}
|
|
276
284
|
}
|
|
277
285
|
|
|
@@ -292,7 +300,7 @@ export const webSearchCustomTool: CustomTool<typeof webSearchSchema, SearchRende
|
|
|
292
300
|
) {
|
|
293
301
|
const authStorage = ctx.modelRegistry?.authStorage ?? (await discoverAuthStorage());
|
|
294
302
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
295
|
-
return executeSearch(toolCallId, params, { authStorage, sessionId, signal });
|
|
303
|
+
return executeSearch(toolCallId, params, { authStorage, model: ctx.model, sessionId, signal });
|
|
296
304
|
},
|
|
297
305
|
|
|
298
306
|
renderCall(args: SearchToolParams, options: RenderResultOptions, theme: Theme) {
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import type { AuthStorage, FetchImpl } from "jeopi-ai";
|
|
1
|
+
import type { Api, AuthStorage, FetchImpl, Model } from "jeopi-ai";
|
|
2
2
|
import type { SearchProviderId, SearchResponse } from "../types";
|
|
3
3
|
|
|
4
|
+
export type GeminiOAuthProvider = "google-gemini-cli" | "google-antigravity";
|
|
5
|
+
|
|
6
|
+
/** Return the OAuth provider owned by a matching Gemini model, if any. */
|
|
7
|
+
export function resolveGeminiOAuthProvider(model?: Model<Api>): GeminiOAuthProvider | undefined {
|
|
8
|
+
if (!model?.id.startsWith("gemini-")) return undefined;
|
|
9
|
+
if (model.provider === "google-antigravity" || model.provider === "google-gemini-cli") {
|
|
10
|
+
return model.provider;
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
/**
|
|
5
16
|
* Shared web search parameters passed to providers.
|
|
6
17
|
*
|
|
@@ -51,6 +62,8 @@ export interface SearchParams {
|
|
|
51
62
|
* caller's agent session when available; otherwise omit.
|
|
52
63
|
*/
|
|
53
64
|
sessionId?: string;
|
|
65
|
+
/** Active session model, when the caller has one. Providers may use it only when it matches their protocol. */
|
|
66
|
+
model?: Model<Api>;
|
|
54
67
|
antigravityEndpointMode?: "auto" | "production" | "sandbox";
|
|
55
68
|
}
|
|
56
69
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* sibling SQLite store and never POSTs the broker sentinel to a Google token
|
|
9
9
|
* endpoint.
|
|
10
10
|
*/
|
|
11
|
-
import { type AuthStorage, type FetchImpl, type OAuthAccess, withOAuthAccess } from "jeopi-ai";
|
|
11
|
+
import { type Api, type AuthStorage, type FetchImpl, type Model, type OAuthAccess, withOAuthAccess } from "jeopi-ai";
|
|
12
12
|
import {
|
|
13
13
|
ANTIGRAVITY_SYSTEM_INSTRUCTION,
|
|
14
14
|
getAntigravityUserAgent,
|
|
@@ -18,8 +18,8 @@ import { fetchWithRetry } from "jeopi-utils";
|
|
|
18
18
|
|
|
19
19
|
import type { SearchCitation, SearchResponse, SearchSource } from "../../../web/search/types";
|
|
20
20
|
import { SearchProviderError } from "../../../web/search/types";
|
|
21
|
-
import type { SearchParams } from "./base";
|
|
22
|
-
import { SearchProvider } from "./base";
|
|
21
|
+
import type { GeminiOAuthProvider, SearchParams } from "./base";
|
|
22
|
+
import { resolveGeminiOAuthProvider, SearchProvider } from "./base";
|
|
23
23
|
import { classifyProviderHttpError, withHardTimeout } from "./utils";
|
|
24
24
|
|
|
25
25
|
const DEFAULT_ENDPOINT = "https://cloudcode-pa.googleapis.com";
|
|
@@ -52,6 +52,7 @@ export interface GeminiSearchParams extends GeminiToolParams {
|
|
|
52
52
|
temperature?: number;
|
|
53
53
|
signal?: AbortSignal;
|
|
54
54
|
authStorage: AuthStorage;
|
|
55
|
+
model?: Model<Api>;
|
|
55
56
|
sessionId?: string;
|
|
56
57
|
fetch?: FetchImpl;
|
|
57
58
|
antigravityEndpointMode?: "auto" | "production" | "sandbox";
|
|
@@ -102,8 +103,10 @@ export async function findGeminiAuth(
|
|
|
102
103
|
authStorage: AuthStorage,
|
|
103
104
|
sessionId: string | undefined,
|
|
104
105
|
signal: AbortSignal | undefined,
|
|
106
|
+
preferredProvider?: GeminiOAuthProvider,
|
|
105
107
|
): Promise<GeminiAuthSeed | null> {
|
|
106
|
-
|
|
108
|
+
const providers = preferredProvider ? [preferredProvider] : GEMINI_PROVIDERS;
|
|
109
|
+
for (const provider of providers) {
|
|
107
110
|
const access = await authStorage.getOAuthAccess(provider, sessionId, { signal });
|
|
108
111
|
if (!access?.accessToken || !access.projectId) continue;
|
|
109
112
|
return { provider, access, projectId: access.projectId };
|
|
@@ -160,13 +163,16 @@ interface CloudCodeResponseChunk {
|
|
|
160
163
|
response?: GeminiModelResponse;
|
|
161
164
|
}
|
|
162
165
|
|
|
163
|
-
async function parseGeminiSearchStream(
|
|
166
|
+
async function parseGeminiSearchStream(
|
|
167
|
+
body: ReadableStream<Uint8Array>,
|
|
168
|
+
fallbackModel: string = DEFAULT_MODEL,
|
|
169
|
+
): Promise<GeminiSearchResult> {
|
|
164
170
|
const answerParts: string[] = [];
|
|
165
171
|
const sources: SearchSource[] = [];
|
|
166
172
|
const citations: SearchCitation[] = [];
|
|
167
173
|
const searchQueries: string[] = [];
|
|
168
174
|
const seenUrls = new Set<string>();
|
|
169
|
-
let model =
|
|
175
|
+
let model = fallbackModel;
|
|
170
176
|
let usage: { inputTokens: number; outputTokens: number; totalTokens: number } | undefined;
|
|
171
177
|
|
|
172
178
|
const reader = body.getReader();
|
|
@@ -287,6 +293,7 @@ async function parseGeminiSearchStream(body: ReadableStream<Uint8Array>): Promis
|
|
|
287
293
|
*/
|
|
288
294
|
async function callGeminiSearch(
|
|
289
295
|
auth: GeminiAuth,
|
|
296
|
+
modelId: string,
|
|
290
297
|
query: string,
|
|
291
298
|
systemPrompt: string | undefined,
|
|
292
299
|
maxOutputTokens: number | undefined,
|
|
@@ -330,7 +337,7 @@ async function callGeminiSearch(
|
|
|
330
337
|
|
|
331
338
|
const requestBody: Record<string, unknown> = {
|
|
332
339
|
project: auth.projectId,
|
|
333
|
-
model:
|
|
340
|
+
model: modelId,
|
|
334
341
|
request: {
|
|
335
342
|
contents: [
|
|
336
343
|
{
|
|
@@ -414,7 +421,7 @@ async function callGeminiSearch(
|
|
|
414
421
|
throw new SearchProviderError("gemini", "Gemini API returned no response body", 500);
|
|
415
422
|
}
|
|
416
423
|
|
|
417
|
-
return parseGeminiSearchStream(response.body);
|
|
424
|
+
return parseGeminiSearchStream(response.body, modelId);
|
|
418
425
|
}
|
|
419
426
|
|
|
420
427
|
async function callGeminiDeveloperSearch(
|
|
@@ -494,7 +501,10 @@ async function callGeminiDeveloperSearch(
|
|
|
494
501
|
* Executes a web search using Google Gemini with Google Search grounding.
|
|
495
502
|
*/
|
|
496
503
|
export async function searchGemini(params: GeminiSearchParams): Promise<SearchResponse> {
|
|
497
|
-
const
|
|
504
|
+
const selectedProvider = resolveGeminiOAuthProvider(params.model);
|
|
505
|
+
const selectedModelId =
|
|
506
|
+
selectedProvider && params.model ? (params.model.requestModelId ?? params.model.id) : DEFAULT_MODEL;
|
|
507
|
+
const seed = await findGeminiAuth(params.authStorage, params.sessionId, params.signal, selectedProvider);
|
|
498
508
|
let result: GeminiSearchResult;
|
|
499
509
|
|
|
500
510
|
if (seed) {
|
|
@@ -513,6 +523,7 @@ export async function searchGemini(params: GeminiSearchParams): Promise<SearchRe
|
|
|
513
523
|
projectId: access.projectId ?? seed.projectId,
|
|
514
524
|
isAntigravity,
|
|
515
525
|
},
|
|
526
|
+
selectedModelId,
|
|
516
527
|
params.query,
|
|
517
528
|
params.system_prompt,
|
|
518
529
|
params.max_output_tokens,
|
|
@@ -528,6 +539,12 @@ export async function searchGemini(params: GeminiSearchParams): Promise<SearchRe
|
|
|
528
539
|
),
|
|
529
540
|
{ sessionId: params.sessionId, signal: params.signal, seed: seed.access },
|
|
530
541
|
);
|
|
542
|
+
} else if (selectedProvider) {
|
|
543
|
+
throw new SearchProviderError(
|
|
544
|
+
"gemini",
|
|
545
|
+
`No OAuth credentials found for selected Gemini provider "${selectedProvider}".`,
|
|
546
|
+
401,
|
|
547
|
+
);
|
|
531
548
|
} else {
|
|
532
549
|
const apiKey = await params.authStorage.getApiKey(DEVELOPER_API_PROVIDER, params.sessionId, {
|
|
533
550
|
signal: params.signal,
|
|
@@ -594,8 +611,10 @@ export class GeminiProvider extends SearchProvider {
|
|
|
594
611
|
url_context: params.urlContext,
|
|
595
612
|
signal: params.signal,
|
|
596
613
|
authStorage: params.authStorage,
|
|
614
|
+
model: params.model,
|
|
597
615
|
sessionId: params.sessionId,
|
|
598
616
|
fetch: params.fetch,
|
|
617
|
+
antigravityEndpointMode: params.antigravityEndpointMode,
|
|
599
618
|
});
|
|
600
619
|
}
|
|
601
620
|
}
|