jeopi-cli 16.5.5 → 16.6.1
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 +19 -0
- package/README.md +80 -0
- package/dist/cli.js +3897 -4058
- package/dist/types/cli/messenger-cli.d.ts +10 -0
- package/dist/types/commands/messenger.d.ts +18 -0
- package/dist/types/messenger/config.d.ts +4 -0
- package/dist/types/messenger/discord.d.ts +10 -0
- package/dist/types/messenger/gateway.d.ts +9 -0
- package/dist/types/messenger/http.d.ts +9 -0
- package/dist/types/messenger/session.d.ts +17 -0
- package/dist/types/messenger/slack.d.ts +2 -0
- package/dist/types/messenger/telegram.d.ts +2 -0
- package/dist/types/messenger/types.d.ts +38 -0
- package/dist/types/tools/builtin-names.d.ts +2 -1
- package/package.json +12 -12
- package/src/cli/messenger-cli.ts +78 -0
- package/src/cli-commands.ts +1 -0
- package/src/commands/messenger.ts +30 -0
- package/src/config/model-registry.ts +110 -26
- package/src/messenger/config.ts +111 -0
- package/src/messenger/discord.ts +382 -0
- package/src/messenger/gateway.ts +153 -0
- package/src/messenger/http.ts +87 -0
- package/src/messenger/session.ts +219 -0
- package/src/messenger/slack.ts +333 -0
- package/src/messenger/telegram.ts +244 -0
- package/src/messenger/types.ts +47 -0
- package/src/modes/components/assistant-message.ts +1 -1
- package/src/modes/controllers/streaming-reveal.ts +21 -2
- package/src/session/session-persistence.ts +22 -4
- package/src/tools/builtin-names.ts +11 -2
- package/src/tools/index.ts +2 -2
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const MESSENGER_ACTIONS: readonly ["check", "run"];
|
|
2
|
+
export type MessengerAction = (typeof MESSENGER_ACTIONS)[number];
|
|
3
|
+
export interface MessengerCommandArgs {
|
|
4
|
+
action: MessengerAction;
|
|
5
|
+
flags: {
|
|
6
|
+
config: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
/** Check is deliberately local-only: it never imports the SDK or starts a transport. */
|
|
10
|
+
export declare function runMessengerCommand(cmd: MessengerCommandArgs): Promise<void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command } from "jeopi-utils/cli";
|
|
2
|
+
export default class Messenger extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static args: {
|
|
5
|
+
action: import("jeopi-utils/cli").ArgDescriptor & {
|
|
6
|
+
description: string;
|
|
7
|
+
required: false;
|
|
8
|
+
options: ("check" | "run")[];
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
static flags: {
|
|
12
|
+
config: import("jeopi-utils/cli").FlagDescriptor<"string"> & {
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
static examples: string[];
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MessengerConfig } from "./types";
|
|
2
|
+
export declare function parseMessengerConfig(value: unknown, baseDir: string): MessengerConfig;
|
|
3
|
+
export declare function loadMessengerConfig(file: string): Promise<MessengerConfig>;
|
|
4
|
+
export declare function validateMessengerEnvironment(config: MessengerConfig, env?: Record<string, string | undefined>): void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MessengerAdapter, MessengerPlatformConfig, MessengerTransportDeps } from "./types";
|
|
2
|
+
export interface DiscordClock {
|
|
3
|
+
/** Schedule once and return an idempotent cancellation function. */
|
|
4
|
+
schedule(callback: () => void, delayMs: number): () => void;
|
|
5
|
+
random(): number;
|
|
6
|
+
}
|
|
7
|
+
export interface DiscordTransportDeps extends MessengerTransportDeps {
|
|
8
|
+
clock?: DiscordClock;
|
|
9
|
+
}
|
|
10
|
+
export declare function createDiscordAdapter(config: MessengerPlatformConfig, env?: Record<string, string | undefined>, deps?: DiscordTransportDeps): MessengerAdapter;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MessengerAdapter, MessengerConfig, MessengerMessage, MessengerReply } from "./types";
|
|
2
|
+
/** Include the sender even in shared threads: remote histories must not cross users. */
|
|
3
|
+
export declare function messengerConversationKey(message: MessengerMessage): string;
|
|
4
|
+
export declare function isMessengerMessageAllowed(message: MessengerMessage, config: MessengerConfig): boolean;
|
|
5
|
+
export interface MessengerGateway {
|
|
6
|
+
run(signal: AbortSignal): Promise<void>;
|
|
7
|
+
handle(message: MessengerMessage, signal?: AbortSignal): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export declare function createMessengerGateway(config: MessengerConfig, adapters: MessengerAdapter[], reply: MessengerReply): MessengerGateway;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class MessengerHttpError extends Error {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
readonly retryAfterMs?: number | undefined;
|
|
4
|
+
constructor(status: number, retryAfterMs?: number | undefined);
|
|
5
|
+
}
|
|
6
|
+
/** Provider limits count UTF-16 units; do not bisect supplementary characters. */
|
|
7
|
+
export declare function splitMessengerText(text: string, limit: number): string[];
|
|
8
|
+
export declare function messengerFetchJson(url: string, init: RequestInit, signal?: AbortSignal, fetchImpl?: typeof fetch): Promise<unknown>;
|
|
9
|
+
export declare function waitForMessengerRetry(ms: number, signal: AbortSignal): Promise<void>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Model } from "jeopi-ai";
|
|
2
|
+
import type { ModelRegistry } from "../config/model-registry";
|
|
3
|
+
import type { AuthStorage } from "../session/auth-storage";
|
|
4
|
+
import { type MessengerConfig, type MessengerReply } from "./types";
|
|
5
|
+
export interface MessengerSessionBridge {
|
|
6
|
+
reply: MessengerReply;
|
|
7
|
+
close(): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
/** Explicit SDK resources for embedded callers; security policy remains bridge-owned. */
|
|
10
|
+
export interface MessengerSessionDependencies {
|
|
11
|
+
agentDir: string;
|
|
12
|
+
authStorage: AuthStorage;
|
|
13
|
+
modelRegistry: ModelRegistry;
|
|
14
|
+
model: Model;
|
|
15
|
+
}
|
|
16
|
+
/** One Main session owns the SDK's process-global lifecycle until gateway shutdown. */
|
|
17
|
+
export declare function createMessengerSessionBridge(config: MessengerConfig, dependencies?: MessengerSessionDependencies): MessengerSessionBridge;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type MessengerPlatform = "discord" | "telegram" | "slack";
|
|
2
|
+
export interface MessengerMessage {
|
|
3
|
+
platform: MessengerPlatform;
|
|
4
|
+
accountId: string;
|
|
5
|
+
channelId: string;
|
|
6
|
+
senderId: string;
|
|
7
|
+
messageId: string;
|
|
8
|
+
text: string;
|
|
9
|
+
threadId?: string;
|
|
10
|
+
direct: boolean;
|
|
11
|
+
mentioned: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface MessengerAdapter {
|
|
14
|
+
platform: MessengerPlatform;
|
|
15
|
+
start(onMessage: (message: MessengerMessage) => Promise<void>, signal: AbortSignal): Promise<void>;
|
|
16
|
+
send(message: MessengerMessage, text: string, signal?: AbortSignal): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
export interface MessengerPlatformConfig {
|
|
19
|
+
tokenEnv: string;
|
|
20
|
+
appTokenEnv?: string;
|
|
21
|
+
allowedUserIds: string[];
|
|
22
|
+
allowedChannelIds?: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface MessengerConfig {
|
|
25
|
+
cwd: string;
|
|
26
|
+
sessionDir: string;
|
|
27
|
+
toolNames: string[];
|
|
28
|
+
platforms: Partial<Record<MessengerPlatform, MessengerPlatformConfig>>;
|
|
29
|
+
}
|
|
30
|
+
export interface MessengerTransportDeps {
|
|
31
|
+
fetch?: typeof fetch;
|
|
32
|
+
WebSocket?: typeof WebSocket;
|
|
33
|
+
}
|
|
34
|
+
export type MessengerReply = (message: MessengerMessage, signal: AbortSignal) => Promise<string>;
|
|
35
|
+
/** A partially initialized SDK must not be retried inside a live gateway. */
|
|
36
|
+
export declare class MessengerFatalError extends Error {
|
|
37
|
+
constructor();
|
|
38
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const BUILTIN_TOOL_NAMES: readonly ["read", "bash", "edit", "ast_grep", "ast_edit", "ask", "debug", "eval", "ssh", "github", "glob", "grep", "lsp", "inspect_image", "vision_verify", "browser", "checkpoint", "rewind", "task", "job", "irc", "subagent", "todo", "web_search", "search_tool_bm25", "write", "memory_edit", "retain", "recall", "reflect", "learn", "manage_skill"];
|
|
2
2
|
export type BuiltinToolName = (typeof BUILTIN_TOOL_NAMES)[number];
|
|
3
|
-
|
|
3
|
+
export declare const HIDDEN_TOOL_NAMES: readonly ["yield", "report_finding", "report_tool_issue", "resolve", "goal"];
|
|
4
|
+
/** Canonicalize built-in IDs and legacy aliases. Leave plugin names unchanged. */
|
|
4
5
|
export declare function normalizeToolName(name: string): string;
|
|
5
6
|
/** Normalize and deduplicate tool names while preserving first-seen order. */
|
|
6
7
|
export declare function normalizeToolNames(names: Iterable<string>): string[];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "jeopi-cli",
|
|
4
|
-
"version": "16.
|
|
4
|
+
"version": "16.6.1",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/akillness/jeopi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -57,17 +57,17 @@
|
|
|
57
57
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
58
58
|
"@babel/parser": "^7.29.7",
|
|
59
59
|
"@mozilla/readability": "^0.6.0",
|
|
60
|
-
"jeopi-hashline": "16.
|
|
61
|
-
"jeopi-stats": "16.
|
|
62
|
-
"jeopi-agent-core": "16.
|
|
63
|
-
"jeopi-ai": "16.
|
|
64
|
-
"jeopi-catalog": "16.
|
|
65
|
-
"jeopi-mnemopi": "16.
|
|
66
|
-
"jeopi-natives": "16.
|
|
67
|
-
"jeopi-tui": "16.
|
|
68
|
-
"jeopi-utils": "16.
|
|
69
|
-
"jeopi-wire": "16.
|
|
70
|
-
"jeopi-snapcompact": "16.
|
|
60
|
+
"jeopi-hashline": "16.6.1",
|
|
61
|
+
"jeopi-stats": "16.6.1",
|
|
62
|
+
"jeopi-agent-core": "16.6.1",
|
|
63
|
+
"jeopi-ai": "16.6.1",
|
|
64
|
+
"jeopi-catalog": "16.6.1",
|
|
65
|
+
"jeopi-mnemopi": "16.6.1",
|
|
66
|
+
"jeopi-natives": "16.6.1",
|
|
67
|
+
"jeopi-tui": "16.6.1",
|
|
68
|
+
"jeopi-utils": "16.6.1",
|
|
69
|
+
"jeopi-wire": "16.6.1",
|
|
70
|
+
"jeopi-snapcompact": "16.6.1",
|
|
71
71
|
"@opentelemetry/api": "^1.9.1",
|
|
72
72
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
73
73
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { loadMessengerConfig, validateMessengerEnvironment } from "../messenger/config";
|
|
2
|
+
import type { MessengerSessionBridge } from "../messenger/session";
|
|
3
|
+
import { type MessengerAdapter, type MessengerConfig, MessengerFatalError } from "../messenger/types";
|
|
4
|
+
|
|
5
|
+
export const MESSENGER_ACTIONS = ["check", "run"] as const;
|
|
6
|
+
export type MessengerAction = (typeof MESSENGER_ACTIONS)[number];
|
|
7
|
+
export interface MessengerCommandArgs {
|
|
8
|
+
action: MessengerAction;
|
|
9
|
+
flags: { config: string };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Check is deliberately local-only: it never imports the SDK or starts a transport. */
|
|
13
|
+
export async function runMessengerCommand(cmd: MessengerCommandArgs): Promise<void> {
|
|
14
|
+
if (!MESSENGER_ACTIONS.includes(cmd.action)) throw new Error("Unknown messenger action");
|
|
15
|
+
if (!cmd.flags.config) throw new Error("Messenger requires --config <file>");
|
|
16
|
+
// Do not echo parse errors, paths, configuration values, or environment secrets.
|
|
17
|
+
let config: MessengerConfig;
|
|
18
|
+
try {
|
|
19
|
+
config = await loadMessengerConfig(cmd.flags.config);
|
|
20
|
+
validateMessengerEnvironment(config);
|
|
21
|
+
} catch {
|
|
22
|
+
throw new Error("Messenger configuration or required environment is invalid");
|
|
23
|
+
}
|
|
24
|
+
const platforms = Object.keys(config.platforms);
|
|
25
|
+
if (cmd.action === "check") {
|
|
26
|
+
process.stdout.write(
|
|
27
|
+
`Messenger configuration valid: ${platforms.join(", ")}; ${config.toolNames.length} tools enabled. No network checks performed.\n`,
|
|
28
|
+
);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (config.toolNames.length > 0) {
|
|
32
|
+
process.stderr.write(
|
|
33
|
+
"Warning: enabled messenger tools grant authorized remote users local machine authority; this is not a sandbox.\n",
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const controller = new AbortController();
|
|
38
|
+
const stop = (): void => controller.abort();
|
|
39
|
+
process.once("SIGINT", stop);
|
|
40
|
+
process.once("SIGTERM", stop);
|
|
41
|
+
let bridge: MessengerSessionBridge | undefined;
|
|
42
|
+
let failure: Error | undefined;
|
|
43
|
+
try {
|
|
44
|
+
const [{ createMessengerSessionBridge }, { createMessengerGateway }, telegram, discord, slack] =
|
|
45
|
+
await Promise.all([
|
|
46
|
+
import("../messenger/session"),
|
|
47
|
+
import("../messenger/gateway"),
|
|
48
|
+
import("../messenger/telegram"),
|
|
49
|
+
import("../messenger/discord"),
|
|
50
|
+
import("../messenger/slack"),
|
|
51
|
+
]);
|
|
52
|
+
if (controller.signal.aborted) return;
|
|
53
|
+
bridge = createMessengerSessionBridge(config);
|
|
54
|
+
const adapters: MessengerAdapter[] = [];
|
|
55
|
+
if (config.platforms.telegram) adapters.push(telegram.createTelegramAdapter(config.platforms.telegram));
|
|
56
|
+
if (config.platforms.discord) adapters.push(discord.createDiscordAdapter(config.platforms.discord));
|
|
57
|
+
if (config.platforms.slack) adapters.push(slack.createSlackAdapter(config.platforms.slack));
|
|
58
|
+
const gateway = createMessengerGateway(config, adapters, bridge.reply);
|
|
59
|
+
process.stdout.write(`Starting messenger: ${platforms.join(", ")}. Press Ctrl+C to stop.\n`);
|
|
60
|
+
await gateway.run(controller.signal);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
failure =
|
|
63
|
+
error instanceof MessengerFatalError
|
|
64
|
+
? error
|
|
65
|
+
: new Error("Messenger gateway failed; check local configuration and provider access");
|
|
66
|
+
} finally {
|
|
67
|
+
controller.abort();
|
|
68
|
+
try {
|
|
69
|
+
await bridge?.close();
|
|
70
|
+
} catch {
|
|
71
|
+
failure ??= new Error("Messenger session shutdown failed");
|
|
72
|
+
} finally {
|
|
73
|
+
process.off("SIGINT", stop);
|
|
74
|
+
process.off("SIGTERM", stop);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (failure) throw failure;
|
|
78
|
+
}
|
package/src/cli-commands.ts
CHANGED
|
@@ -16,6 +16,7 @@ export const commands: CommandEntry[] = [
|
|
|
16
16
|
{ name: "acp", load: () => import("./commands/acp").then(m => m.default) },
|
|
17
17
|
{ name: "auth-broker", load: () => import("./commands/auth-broker").then(m => m.default) },
|
|
18
18
|
{ name: "auth-gateway", load: () => import("./commands/auth-gateway").then(m => m.default) },
|
|
19
|
+
{ name: "messenger", load: () => import("./commands/messenger").then(m => m.default) },
|
|
19
20
|
{ name: "agents", load: () => import("./commands/agents").then(m => m.default) },
|
|
20
21
|
{ name: "bench", load: () => import("./commands/bench").then(m => m.default) },
|
|
21
22
|
{ name: "commit", load: () => import("./commands/commit").then(m => m.default) },
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { APP_NAME } from "jeopi-utils";
|
|
2
|
+
import { Args, Command, Flags, renderCommandHelp } from "jeopi-utils/cli";
|
|
3
|
+
import { MESSENGER_ACTIONS, type MessengerAction, runMessengerCommand } from "../cli/messenger-cli";
|
|
4
|
+
|
|
5
|
+
export default class Messenger extends Command {
|
|
6
|
+
static description = "Run authorized Discord, Telegram, and Slack text bots";
|
|
7
|
+
|
|
8
|
+
static args = {
|
|
9
|
+
action: Args.string({ description: "Sub-command", required: false, options: [...MESSENGER_ACTIONS] }),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
static flags = {
|
|
13
|
+
config: Flags.string({ description: "Path to messenger JSON configuration" }),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
static examples = [
|
|
17
|
+
"jeopi messenger check --config ./messenger.json",
|
|
18
|
+
"jeopi messenger run --config ./messenger.json",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
async run(): Promise<void> {
|
|
22
|
+
const { args, flags } = await this.parse(Messenger);
|
|
23
|
+
if (!args.action) {
|
|
24
|
+
renderCommandHelp(APP_NAME, "messenger", Messenger);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!flags.config) throw new Error("Messenger requires --config <file>");
|
|
28
|
+
await runMessengerCommand({ action: args.action as MessengerAction, flags: { config: flags.config } });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -14,8 +14,13 @@ import type { AssistantMessageEventStream } from "jeopi-ai/utils/event-stream";
|
|
|
14
14
|
import { buildModel } from "jeopi-catalog/build";
|
|
15
15
|
import { isVertexExpressOpenAIUrl } from "jeopi-catalog/hosts";
|
|
16
16
|
import { readModelCache } from "jeopi-catalog/model-cache";
|
|
17
|
-
import {
|
|
18
|
-
|
|
17
|
+
import {
|
|
18
|
+
createModelManager,
|
|
19
|
+
type ModelManagerOptions,
|
|
20
|
+
type ModelRefreshStrategy,
|
|
21
|
+
restoreCachedModelHeaders,
|
|
22
|
+
} from "jeopi-catalog/model-manager";
|
|
23
|
+
import { type GeneratedProvider, getBundledModels, getBundledProviders } from "jeopi-catalog/models";
|
|
19
24
|
import {
|
|
20
25
|
googleAntigravityModelManagerOptions,
|
|
21
26
|
googleGeminiCliModelManagerOptions,
|
|
@@ -940,6 +945,9 @@ export class ModelRegistry {
|
|
|
940
945
|
this.#addImplicitDiscoverableProviders(configuredProviders);
|
|
941
946
|
let builtInModels = this.#applyHardcodedModelPolicies(this.#loadBuiltInModels(overrides));
|
|
942
947
|
const cachedStandardResult = this.#loadCachedStandardProviderModels();
|
|
948
|
+
builtInModels = builtInModels.filter(
|
|
949
|
+
model => !cachedStandardResult.unresolvedModelIdsByProvider.get(model.provider)?.has(model.id),
|
|
950
|
+
);
|
|
943
951
|
const cachedStandardModels = this.#applyHardcodedModelPolicies(cachedStandardResult.models);
|
|
944
952
|
const cachedDiscoveries = this.#applyHardcodedModelPolicies(this.#loadCachedDiscoverableModels());
|
|
945
953
|
// Only drop bundled fallback models when the cached project-catalog row is
|
|
@@ -1057,10 +1065,22 @@ export class ModelRegistry {
|
|
|
1057
1065
|
return descriptor.createModelManagerOptions({ baseUrl, fetch: this.#fetch }).cacheProviderId ?? providerId;
|
|
1058
1066
|
}
|
|
1059
1067
|
|
|
1060
|
-
#
|
|
1068
|
+
#restoreConfiguredModelHeaders(model: Model<Api>): Record<string, string> | undefined {
|
|
1069
|
+
const override = this.#providerOverrides.get(model.provider);
|
|
1070
|
+
const withoutHeaders = { ...model, headers: undefined };
|
|
1071
|
+
const configured = override ? this.#applyProviderTransportOverride(withoutHeaders, override) : withoutHeaders;
|
|
1072
|
+
return this.#applyProviderModelOverrides(model.provider, [configured])[0]?.headers;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
#loadCachedStandardProviderModels(): {
|
|
1076
|
+
models: Model<Api>[];
|
|
1077
|
+
authoritativeFreshProviders: Set<string>;
|
|
1078
|
+
unresolvedModelIdsByProvider: Map<string, ReadonlySet<string>>;
|
|
1079
|
+
} {
|
|
1061
1080
|
const configuredDiscoveryProviders = new Set(this.#discoverableProviders.map(provider => provider.provider));
|
|
1062
1081
|
const cachedModels: Model<Api>[] = [];
|
|
1063
1082
|
const authoritativeFreshProviders = new Set<string>();
|
|
1083
|
+
const unresolvedModelIdsByProvider = new Map<string, ReadonlySet<string>>();
|
|
1064
1084
|
for (const providerId of STARTUP_MODEL_CACHE_PROVIDER_IDS) {
|
|
1065
1085
|
if (configuredDiscoveryProviders.has(providerId)) {
|
|
1066
1086
|
continue;
|
|
@@ -1070,12 +1090,24 @@ export class ModelRegistry {
|
|
|
1070
1090
|
if (!cache) {
|
|
1071
1091
|
continue;
|
|
1072
1092
|
}
|
|
1073
|
-
|
|
1093
|
+
const descriptor = PROVIDER_DESCRIPTORS.find(candidate => candidate.providerId === providerId);
|
|
1094
|
+
const restored = restoreCachedModelHeaders(
|
|
1095
|
+
cache.models,
|
|
1096
|
+
getBundledModels(providerId as GeneratedProvider),
|
|
1097
|
+
cache.headerOmittedModelIds,
|
|
1098
|
+
cache.unrestorableHeaderModelIds,
|
|
1099
|
+
descriptor?.createModelManagerOptions({}).restorableHeaderFallback,
|
|
1100
|
+
model => this.#restoreConfiguredModelHeaders(model),
|
|
1101
|
+
);
|
|
1102
|
+
if (restored.unresolvedModelIds.size > 0) {
|
|
1103
|
+
unresolvedModelIdsByProvider.set(providerId, restored.unresolvedModelIds);
|
|
1104
|
+
}
|
|
1105
|
+
if (cache.fresh && cache.authoritative && restored.unresolvedModelIds.size === 0) {
|
|
1074
1106
|
authoritativeFreshProviders.add(providerId);
|
|
1075
1107
|
}
|
|
1076
|
-
const models =
|
|
1077
|
-
|
|
1078
|
-
|
|
1108
|
+
const models = restored.models
|
|
1109
|
+
.filter(model => !restored.unresolvedModelIds.has(model.id))
|
|
1110
|
+
.map(model => (model.provider === providerId ? model : { ...model, provider: providerId }));
|
|
1079
1111
|
const providerOverride = this.#providerOverrides.get(providerId);
|
|
1080
1112
|
const withTransport = providerOverride
|
|
1081
1113
|
? models.map(model => this.#applyProviderTransportOverride(model, providerOverride))
|
|
@@ -1090,7 +1122,7 @@ export class ModelRegistry {
|
|
|
1090
1122
|
: withTransport.map(model => buildModel(model));
|
|
1091
1123
|
cachedModels.push(...this.#applyProviderModelOverrides(providerId, withCompat));
|
|
1092
1124
|
}
|
|
1093
|
-
return { models: cachedModels, authoritativeFreshProviders };
|
|
1125
|
+
return { models: cachedModels, authoritativeFreshProviders, unresolvedModelIdsByProvider };
|
|
1094
1126
|
}
|
|
1095
1127
|
|
|
1096
1128
|
#loadCachedDiscoverableModels(): Model<Api>[] {
|
|
@@ -1112,6 +1144,14 @@ export class ModelRegistry {
|
|
|
1112
1144
|
});
|
|
1113
1145
|
continue;
|
|
1114
1146
|
}
|
|
1147
|
+
const restored = restoreCachedModelHeaders(
|
|
1148
|
+
cache.models,
|
|
1149
|
+
[],
|
|
1150
|
+
cache.headerOmittedModelIds,
|
|
1151
|
+
cache.unrestorableHeaderModelIds,
|
|
1152
|
+
undefined,
|
|
1153
|
+
model => this.#restoreConfiguredModelHeaders(model),
|
|
1154
|
+
);
|
|
1115
1155
|
const configStale = this.#isDiscoveryCacheOlderThanModelsConfig(cache.updatedAt);
|
|
1116
1156
|
const models = this.#applyProviderModelOverrides(
|
|
1117
1157
|
providerConfig.provider,
|
|
@@ -1119,7 +1159,7 @@ export class ModelRegistry {
|
|
|
1119
1159
|
providerConfig,
|
|
1120
1160
|
this.#applyProviderCompat(
|
|
1121
1161
|
providerConfig.compat,
|
|
1122
|
-
|
|
1162
|
+
restored.models.filter(model => !restored.unresolvedModelIds.has(model.id)),
|
|
1123
1163
|
),
|
|
1124
1164
|
),
|
|
1125
1165
|
);
|
|
@@ -1128,7 +1168,12 @@ export class ModelRegistry {
|
|
|
1128
1168
|
provider: providerConfig.provider,
|
|
1129
1169
|
status: "cached",
|
|
1130
1170
|
optional: providerConfig.optional ?? false,
|
|
1131
|
-
stale:
|
|
1171
|
+
stale:
|
|
1172
|
+
providerConfig.discovery.type === "llama.cpp" ||
|
|
1173
|
+
!cache.fresh ||
|
|
1174
|
+
!cache.authoritative ||
|
|
1175
|
+
configStale ||
|
|
1176
|
+
restored.unresolvedModelIds.size > 0,
|
|
1132
1177
|
fetchedAt: cache.updatedAt,
|
|
1133
1178
|
models: models.map(model => model.id),
|
|
1134
1179
|
});
|
|
@@ -1144,14 +1189,18 @@ export class ModelRegistry {
|
|
|
1144
1189
|
}
|
|
1145
1190
|
|
|
1146
1191
|
#normalizeDiscoverableModels(providerConfig: DiscoveryProviderConfig, models: Model<Api>[]): Model<Api>[] {
|
|
1192
|
+
const override = this.#providerOverrides.get(providerConfig.provider);
|
|
1193
|
+
const withTransport = override
|
|
1194
|
+
? models.map(model => this.#applyProviderTransportOverride(model, override))
|
|
1195
|
+
: models;
|
|
1147
1196
|
const withDecoderMetadata =
|
|
1148
1197
|
providerConfig.discovery.type === "ollama" ||
|
|
1149
1198
|
providerConfig.discovery.type === "llama.cpp" ||
|
|
1150
1199
|
providerConfig.discovery.type === "lm-studio"
|
|
1151
|
-
?
|
|
1200
|
+
? withTransport.map(model =>
|
|
1152
1201
|
buildModel({ ...model, imageInputDecoder: "stb", compat: model.compatConfig } as ModelSpec<Api>),
|
|
1153
1202
|
)
|
|
1154
|
-
:
|
|
1203
|
+
: withTransport;
|
|
1155
1204
|
|
|
1156
1205
|
const withRemoteCompaction = providerConfig.remoteCompaction
|
|
1157
1206
|
? withDecoderMetadata.map(model =>
|
|
@@ -1430,6 +1479,15 @@ export class ModelRegistry {
|
|
|
1430
1479
|
): Promise<Model<Api>[]> {
|
|
1431
1480
|
const cacheProviderId = this.#configuredDiscoveryCacheProviderId(providerConfig);
|
|
1432
1481
|
const cached = readModelCache<Api>(cacheProviderId, 24 * 60 * 60 * 1000, Date.now, this.#cacheDbPath);
|
|
1482
|
+
const restored = restoreCachedModelHeaders(
|
|
1483
|
+
cached?.models ?? [],
|
|
1484
|
+
[],
|
|
1485
|
+
cached?.headerOmittedModelIds ?? [],
|
|
1486
|
+
cached?.unrestorableHeaderModelIds ?? [],
|
|
1487
|
+
undefined,
|
|
1488
|
+
model => this.#restoreConfiguredModelHeaders(model),
|
|
1489
|
+
);
|
|
1490
|
+
const usableCachedModels = restored.models.filter(model => !restored.unresolvedModelIds.has(model.id));
|
|
1433
1491
|
const cacheOlderThanConfig = cached !== null && this.#isDiscoveryCacheOlderThanModelsConfig(cached.updatedAt);
|
|
1434
1492
|
const shouldForceOnlineDiscovery =
|
|
1435
1493
|
strategy === "online-if-uncached" && (cacheOlderThanConfig || isAuthoritativeLocalDiscovery(providerConfig));
|
|
@@ -1444,15 +1502,10 @@ export class ModelRegistry {
|
|
|
1444
1502
|
optional: providerConfig.optional ?? false,
|
|
1445
1503
|
stale: cached !== null,
|
|
1446
1504
|
fetchedAt: cached?.updatedAt,
|
|
1447
|
-
models:
|
|
1505
|
+
models: usableCachedModels.map(model => model.id),
|
|
1448
1506
|
});
|
|
1449
1507
|
this.#lastDiscoveryWarnings.delete(providerConfig.provider);
|
|
1450
|
-
return cached
|
|
1451
|
-
? this.#normalizeDiscoverableModels(
|
|
1452
|
-
providerConfig,
|
|
1453
|
-
cached.models.map(model => buildModel(model)),
|
|
1454
|
-
)
|
|
1455
|
-
: [];
|
|
1508
|
+
return cached ? this.#normalizeDiscoverableModels(providerConfig, usableCachedModels) : [];
|
|
1456
1509
|
}
|
|
1457
1510
|
}
|
|
1458
1511
|
|
|
@@ -1462,7 +1515,10 @@ export class ModelRegistry {
|
|
|
1462
1515
|
try {
|
|
1463
1516
|
const models = this.#applyProviderModelOverrides(
|
|
1464
1517
|
providerId,
|
|
1465
|
-
|
|
1518
|
+
this.#normalizeDiscoverableModels(
|
|
1519
|
+
providerConfig,
|
|
1520
|
+
await discoverModelsByProviderType(providerConfig, this.#discoveryContext()),
|
|
1521
|
+
),
|
|
1466
1522
|
);
|
|
1467
1523
|
this.#lastDiscoveryWarnings.delete(providerId);
|
|
1468
1524
|
return models.map(toModelSpec);
|
|
@@ -1479,6 +1535,7 @@ export class ModelRegistry {
|
|
|
1479
1535
|
cacheProviderId,
|
|
1480
1536
|
cacheTtlMs: 24 * 60 * 60 * 1000,
|
|
1481
1537
|
fetchDynamicModels,
|
|
1538
|
+
restoreCachedHeaders: model => this.#restoreConfiguredModelHeaders(model),
|
|
1482
1539
|
});
|
|
1483
1540
|
const result = await manager.refresh(effectiveStrategy);
|
|
1484
1541
|
const status = discoveryError
|
|
@@ -1586,13 +1643,40 @@ export class ModelRegistry {
|
|
|
1586
1643
|
}> = [
|
|
1587
1644
|
{
|
|
1588
1645
|
providerId: "google-antigravity",
|
|
1589
|
-
resolveKey:
|
|
1590
|
-
|
|
1591
|
-
|
|
1646
|
+
resolveKey: value =>
|
|
1647
|
+
extractGoogleOAuthToken(value) ?? this.authStorage.getOAuthCredential("google-antigravity")?.access,
|
|
1648
|
+
createOptions: oauthToken => {
|
|
1649
|
+
// Pin discovery to one stable account, including when its access token has expired.
|
|
1650
|
+
// Independent peek/getApiKey calls otherwise advance round-robin and poison the cache scope.
|
|
1651
|
+
const account = this.authStorage.listOAuthAccounts("google-antigravity")[0];
|
|
1652
|
+
const cacheScope = account
|
|
1653
|
+
? JSON.stringify([account.credentialId, account.accountId ?? account.email, account.projectId])
|
|
1654
|
+
: undefined;
|
|
1655
|
+
return googleAntigravityModelManagerOptions({
|
|
1592
1656
|
oauthToken,
|
|
1593
|
-
|
|
1657
|
+
cacheScope,
|
|
1658
|
+
endpoint:
|
|
1659
|
+
this.#runtimeProviderOverrides.get("google-antigravity")?.baseUrl ??
|
|
1660
|
+
this.#providerOverrides.get("google-antigravity")?.baseUrl ??
|
|
1661
|
+
this.getProviderBaseUrl("google-antigravity"),
|
|
1594
1662
|
fetch: this.#fetch,
|
|
1595
|
-
|
|
1663
|
+
resolveOAuthToken: async signal => {
|
|
1664
|
+
if (!account) {
|
|
1665
|
+
return extractGoogleOAuthToken(
|
|
1666
|
+
await this.getApiKeyForProvider("google-antigravity", undefined, { signal }),
|
|
1667
|
+
);
|
|
1668
|
+
}
|
|
1669
|
+
const current = this.authStorage
|
|
1670
|
+
.listOAuthAccounts("google-antigravity")
|
|
1671
|
+
.find(entry => entry.credentialId === account.credentialId);
|
|
1672
|
+
if (!current) return undefined;
|
|
1673
|
+
const access = await this.authStorage.getOAuthAccessAt("google-antigravity", current.position, {
|
|
1674
|
+
signal,
|
|
1675
|
+
});
|
|
1676
|
+
return access?.ok ? access.accessToken : undefined;
|
|
1677
|
+
},
|
|
1678
|
+
});
|
|
1679
|
+
},
|
|
1596
1680
|
},
|
|
1597
1681
|
{
|
|
1598
1682
|
providerId: "google-gemini-cli",
|
|
@@ -1690,7 +1774,7 @@ export class ModelRegistry {
|
|
|
1690
1774
|
model.provider === options.providerId ? model : { ...model, provider: options.providerId },
|
|
1691
1775
|
);
|
|
1692
1776
|
const authoritativeProviders = new Set<string>();
|
|
1693
|
-
if (options.dynamicModelsAuthoritative && !result.stale) {
|
|
1777
|
+
if (options.dynamicModelsAuthoritative && (!result.stale || result.preservedAuthoritativeCache)) {
|
|
1694
1778
|
authoritativeProviders.add(options.providerId);
|
|
1695
1779
|
}
|
|
1696
1780
|
return { models, authoritativeProviders };
|