om-memory-system 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +587 -0
- package/dist/adapters/opencode/auto-capture-host.d.ts +3 -0
- package/dist/adapters/opencode/auto-capture-host.js +29 -0
- package/dist/adapters/opencode/auto-capture-summary.d.ts +4 -0
- package/dist/adapters/opencode/auto-capture-summary.js +147 -0
- package/dist/adapters/opencode/conversation.d.ts +2 -0
- package/dist/adapters/opencode/conversation.js +50 -0
- package/dist/adapters/opencode/user-prompt.d.ts +9 -0
- package/dist/adapters/opencode/user-prompt.js +26 -0
- package/dist/adapters/pi/capture.d.ts +41 -0
- package/dist/adapters/pi/capture.js +91 -0
- package/dist/adapters/pi/conversation.d.ts +38 -0
- package/dist/adapters/pi/conversation.js +118 -0
- package/dist/adapters/pi/extension.d.ts +18 -0
- package/dist/adapters/pi/extension.js +234 -0
- package/dist/adapters/pi/import-command.d.ts +29 -0
- package/dist/adapters/pi/import-command.js +232 -0
- package/dist/adapters/pi/profile.d.ts +39 -0
- package/dist/adapters/pi/profile.js +148 -0
- package/dist/adapters/pi/provider.d.ts +50 -0
- package/dist/adapters/pi/provider.js +77 -0
- package/dist/config.d.ts +290 -0
- package/dist/config.js +742 -0
- package/dist/core/capture-context.d.ts +4 -0
- package/dist/core/capture-context.js +132 -0
- package/dist/core/capture.d.ts +15 -0
- package/dist/core/capture.js +68 -0
- package/dist/core/extraction.d.ts +80 -0
- package/dist/core/extraction.js +140 -0
- package/dist/core/host.d.ts +52 -0
- package/dist/core/host.js +1 -0
- package/dist/core/memory-operations.d.ts +27 -0
- package/dist/core/memory-operations.js +247 -0
- package/dist/core/retrieval.d.ts +12 -0
- package/dist/core/retrieval.js +72 -0
- package/dist/importer/discovery.d.ts +31 -0
- package/dist/importer/discovery.js +88 -0
- package/dist/importer/importer.d.ts +87 -0
- package/dist/importer/importer.js +260 -0
- package/dist/importer/ledger.d.ts +44 -0
- package/dist/importer/ledger.js +137 -0
- package/dist/importer/session-loader.d.ts +19 -0
- package/dist/importer/session-loader.js +12 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +601 -0
- package/dist/plugin.d.ts +9 -0
- package/dist/plugin.js +11 -0
- package/dist/services/ai/ai-provider-factory.d.ts +7 -0
- package/dist/services/ai/ai-provider-factory.js +41 -0
- package/dist/services/ai/api-key-placeholder.d.ts +1 -0
- package/dist/services/ai/api-key-placeholder.js +14 -0
- package/dist/services/ai/internal-capture-sessions.d.ts +6 -0
- package/dist/services/ai/internal-capture-sessions.js +33 -0
- package/dist/services/ai/opencode-diagnostics.d.ts +7 -0
- package/dist/services/ai/opencode-diagnostics.js +36 -0
- package/dist/services/ai/opencode-host-config.d.ts +10 -0
- package/dist/services/ai/opencode-host-config.js +51 -0
- package/dist/services/ai/opencode-provider-loader.d.ts +3 -0
- package/dist/services/ai/opencode-provider-loader.js +4 -0
- package/dist/services/ai/opencode-provider.d.ts +92 -0
- package/dist/services/ai/opencode-provider.js +419 -0
- package/dist/services/ai/opencode-sdk-client.d.ts +6 -0
- package/dist/services/ai/opencode-sdk-client.js +38 -0
- package/dist/services/ai/profile-llm-client.d.ts +2 -0
- package/dist/services/ai/profile-llm-client.js +27 -0
- package/dist/services/ai/provider-config.d.ts +17 -0
- package/dist/services/ai/provider-config.js +30 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts +34 -0
- package/dist/services/ai/providers/anthropic-messages.js +220 -0
- package/dist/services/ai/providers/base-provider.d.ts +24 -0
- package/dist/services/ai/providers/base-provider.js +24 -0
- package/dist/services/ai/providers/google-gemini.d.ts +15 -0
- package/dist/services/ai/providers/google-gemini.js +228 -0
- package/dist/services/ai/providers/minimax.d.ts +35 -0
- package/dist/services/ai/providers/minimax.js +55 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts +22 -0
- package/dist/services/ai/providers/openai-chat-completion.js +387 -0
- package/dist/services/ai/providers/openai-responses.d.ts +13 -0
- package/dist/services/ai/providers/openai-responses.js +182 -0
- package/dist/services/ai/providers/orcarouter.d.ts +51 -0
- package/dist/services/ai/providers/orcarouter.js +66 -0
- package/dist/services/ai/session/ai-session-manager.d.ts +24 -0
- package/dist/services/ai/session/ai-session-manager.js +211 -0
- package/dist/services/ai/session/session-types.d.ts +42 -0
- package/dist/services/ai/session/session-types.js +1 -0
- package/dist/services/ai/tools/tool-schema.d.ts +40 -0
- package/dist/services/ai/tools/tool-schema.js +24 -0
- package/dist/services/ai/validators/user-profile-validator.d.ts +12 -0
- package/dist/services/ai/validators/user-profile-validator.js +114 -0
- package/dist/services/api-handlers.d.ts +186 -0
- package/dist/services/api-handlers.js +1284 -0
- package/dist/services/auth-token.d.ts +14 -0
- package/dist/services/auth-token.js +59 -0
- package/dist/services/auto-capture.d.ts +3 -0
- package/dist/services/auto-capture.js +127 -0
- package/dist/services/cleanup-service.d.ts +22 -0
- package/dist/services/cleanup-service.js +109 -0
- package/dist/services/client.d.ts +134 -0
- package/dist/services/client.js +327 -0
- package/dist/services/context.d.ts +10 -0
- package/dist/services/context.js +27 -0
- package/dist/services/cors.d.ts +12 -0
- package/dist/services/cors.js +52 -0
- package/dist/services/deduplication-service.d.ts +28 -0
- package/dist/services/deduplication-service.js +111 -0
- package/dist/services/embedding.d.ts +36 -0
- package/dist/services/embedding.js +200 -0
- package/dist/services/jsonc.d.ts +6 -0
- package/dist/services/jsonc.js +76 -0
- package/dist/services/language-detector.d.ts +2 -0
- package/dist/services/language-detector.js +33 -0
- package/dist/services/legacy-migration.d.ts +77 -0
- package/dist/services/legacy-migration.js +343 -0
- package/dist/services/logger.d.ts +1 -0
- package/dist/services/logger.js +119 -0
- package/dist/services/memory-portability-service.d.ts +37 -0
- package/dist/services/memory-portability-service.js +340 -0
- package/dist/services/memory-scope.d.ts +16 -0
- package/dist/services/memory-scope.js +43 -0
- package/dist/services/migration-service.d.ts +42 -0
- package/dist/services/migration-service.js +307 -0
- package/dist/services/onnxruntime-resolve.d.ts +24 -0
- package/dist/services/onnxruntime-resolve.js +174 -0
- package/dist/services/privacy.d.ts +26 -0
- package/dist/services/privacy.js +69 -0
- package/dist/services/request-timeouts.d.ts +9 -0
- package/dist/services/request-timeouts.js +11 -0
- package/dist/services/runtime-require.d.ts +17 -0
- package/dist/services/runtime-require.js +75 -0
- package/dist/services/secret-resolver.d.ts +1 -0
- package/dist/services/secret-resolver.js +65 -0
- package/dist/services/shard-inventory-service.d.ts +69 -0
- package/dist/services/shard-inventory-service.js +315 -0
- package/dist/services/shard-path-migration-service.d.ts +31 -0
- package/dist/services/shard-path-migration-service.js +367 -0
- package/dist/services/tag-prefix-migration.d.ts +60 -0
- package/dist/services/tag-prefix-migration.js +439 -0
- package/dist/services/tag-prefix-scan.d.ts +26 -0
- package/dist/services/tag-prefix-scan.js +91 -0
- package/dist/services/tags.d.ts +29 -0
- package/dist/services/tags.js +251 -0
- package/dist/services/turso/connection-manager.d.ts +15 -0
- package/dist/services/turso/connection-manager.js +163 -0
- package/dist/services/turso/cross-process-write-lock.d.ts +1 -0
- package/dist/services/turso/cross-process-write-lock.js +89 -0
- package/dist/services/turso/legacy-migrator.d.ts +1 -0
- package/dist/services/turso/legacy-migrator.js +540 -0
- package/dist/services/turso/lifecycle.d.ts +1 -0
- package/dist/services/turso/lifecycle.js +14 -0
- package/dist/services/turso/operation-lock.d.ts +2 -0
- package/dist/services/turso/operation-lock.js +61 -0
- package/dist/services/turso/ready.d.ts +2 -0
- package/dist/services/turso/ready.js +36 -0
- package/dist/services/turso/shard-manager.d.ts +38 -0
- package/dist/services/turso/shard-manager.js +496 -0
- package/dist/services/turso/sqlite-handle-release.d.ts +16 -0
- package/dist/services/turso/sqlite-handle-release.js +56 -0
- package/dist/services/turso/turso-db.d.ts +18 -0
- package/dist/services/turso/turso-db.js +53 -0
- package/dist/services/turso/types.d.ts +42 -0
- package/dist/services/turso/types.js +1 -0
- package/dist/services/turso/vector-search.d.ts +41 -0
- package/dist/services/turso/vector-search.js +384 -0
- package/dist/services/turso/vector-utils.d.ts +7 -0
- package/dist/services/turso/vector-utils.js +67 -0
- package/dist/services/user-memory-learning.d.ts +97 -0
- package/dist/services/user-memory-learning.js +673 -0
- package/dist/services/user-profile/ai-cleanup.d.ts +58 -0
- package/dist/services/user-profile/ai-cleanup.js +486 -0
- package/dist/services/user-profile/profile-context.d.ts +1 -0
- package/dist/services/user-profile/profile-context.js +105 -0
- package/dist/services/user-profile/profile-utils.d.ts +2 -0
- package/dist/services/user-profile/profile-utils.js +45 -0
- package/dist/services/user-profile/types.d.ts +92 -0
- package/dist/services/user-profile/types.js +1 -0
- package/dist/services/user-profile/user-profile-manager.d.ts +65 -0
- package/dist/services/user-profile/user-profile-manager.js +1660 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts +53 -0
- package/dist/services/user-prompt/user-prompt-manager.js +303 -0
- package/dist/services/web-api-auth.d.ts +3 -0
- package/dist/services/web-api-auth.js +25 -0
- package/dist/services/web-auth.d.ts +46 -0
- package/dist/services/web-auth.js +105 -0
- package/dist/services/web-server.d.ts +57 -0
- package/dist/services/web-server.js +659 -0
- package/dist/shared/api/index.d.ts +4 -0
- package/dist/shared/api/index.js +2 -0
- package/dist/shared/api/portability-schemas.d.ts +56 -0
- package/dist/shared/api/portability-schemas.js +39 -0
- package/dist/shared/api/schemas.d.ts +295 -0
- package/dist/shared/api/schemas.js +134 -0
- package/dist/shared/api/types.d.ts +28 -0
- package/dist/shared/api/types.js +1 -0
- package/dist/types/index.d.ts +27 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/context-limit.d.ts +10 -0
- package/dist/utils/context-limit.js +43 -0
- package/dist/utils/math.d.ts +7 -0
- package/dist/utils/math.js +48 -0
- package/dist/utils/profile.d.ts +2 -0
- package/dist/utils/profile.js +15 -0
- package/dist/v2/adapter.d.ts +5 -0
- package/dist/v2/adapter.js +218 -0
- package/dist/v2/legacy-client.d.ts +15 -0
- package/dist/v2/legacy-client.js +257 -0
- package/dist/v2/memory-bridge.d.ts +18 -0
- package/dist/v2/memory-bridge.js +29 -0
- package/dist/v2/plugin.d.ts +3 -0
- package/dist/v2/plugin.js +24 -0
- package/dist/web/assets/index-DPjEpD1h.css +2 -0
- package/dist/web/assets/index-do26c4JJ.js +122 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +14 -0
- package/dist/web/omms-icon.png +0 -0
- package/package.json +138 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { OpencodeClient } from "@opencode-ai/sdk/v2/client";
|
|
2
|
+
export type HostTransport = {
|
|
3
|
+
readonly fetch?: typeof fetch;
|
|
4
|
+
readonly headers?: RequestInit["headers"];
|
|
5
|
+
};
|
|
6
|
+
export declare function createLazyV2Client(baseUrl: string, transport?: HostTransport): OpencodeClient;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function getOpencodeSdkClientSpecifier() {
|
|
2
|
+
return ["@opencode-ai/sdk", "/v2/client"].join("");
|
|
3
|
+
}
|
|
4
|
+
async function createSdkClient(baseUrl, transport) {
|
|
5
|
+
const sdk = (await import(getOpencodeSdkClientSpecifier()));
|
|
6
|
+
return sdk.createOpencodeClient({
|
|
7
|
+
baseUrl,
|
|
8
|
+
...(transport?.fetch ? { fetch: transport.fetch } : {}),
|
|
9
|
+
...(transport?.headers ? { headers: transport.headers } : {}),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export function createLazyV2Client(baseUrl, transport) {
|
|
13
|
+
let sdkClientPromise;
|
|
14
|
+
const getSdkClient = () => {
|
|
15
|
+
sdkClientPromise ??= createSdkClient(baseUrl, transport);
|
|
16
|
+
return sdkClientPromise;
|
|
17
|
+
};
|
|
18
|
+
return {
|
|
19
|
+
session: {
|
|
20
|
+
create: async (...args) => {
|
|
21
|
+
const client = await getSdkClient();
|
|
22
|
+
return client.session.create(...args);
|
|
23
|
+
},
|
|
24
|
+
prompt: async (...args) => {
|
|
25
|
+
const client = await getSdkClient();
|
|
26
|
+
return client.session.prompt(...args);
|
|
27
|
+
},
|
|
28
|
+
delete: async (...args) => {
|
|
29
|
+
const client = await getSdkClient();
|
|
30
|
+
return client.session.delete(...args);
|
|
31
|
+
},
|
|
32
|
+
abort: async (...args) => {
|
|
33
|
+
const client = await getSdkClient();
|
|
34
|
+
return client.session.abort(...args);
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CONFIG } from "../../config.js";
|
|
2
|
+
import { loadOpencodeProvider } from "./opencode-provider-loader.js";
|
|
3
|
+
let _cachedClient = null;
|
|
4
|
+
let _cachedProvider = null;
|
|
5
|
+
let _cachedModel = null;
|
|
6
|
+
export async function getOpenCodeClient() {
|
|
7
|
+
const provider = CONFIG.opencodeProvider;
|
|
8
|
+
const model = CONFIG.opencodeModel;
|
|
9
|
+
if (!provider || !model) {
|
|
10
|
+
throw new Error("omms: opencodeProvider and opencodeModel must be configured");
|
|
11
|
+
}
|
|
12
|
+
if (_cachedClient && _cachedProvider === provider && _cachedModel === model) {
|
|
13
|
+
return _cachedClient;
|
|
14
|
+
}
|
|
15
|
+
const { isProviderConnected, getV2Client } = await loadOpencodeProvider();
|
|
16
|
+
if (!isProviderConnected(provider)) {
|
|
17
|
+
throw new Error(`opencode provider '${provider}' is not connected. Check your opencode provider configuration.`);
|
|
18
|
+
}
|
|
19
|
+
const client = getV2Client();
|
|
20
|
+
if (!client) {
|
|
21
|
+
throw new Error("omms: v2 client not initialized");
|
|
22
|
+
}
|
|
23
|
+
_cachedClient = client;
|
|
24
|
+
_cachedProvider = provider;
|
|
25
|
+
_cachedModel = model;
|
|
26
|
+
return client;
|
|
27
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ProviderConfig } from "./providers/base-provider.js";
|
|
2
|
+
interface MemoryProviderRuntimeConfig {
|
|
3
|
+
memoryProvider?: string;
|
|
4
|
+
memoryModel?: string;
|
|
5
|
+
memoryApiUrl?: string;
|
|
6
|
+
memoryApiKey?: string;
|
|
7
|
+
memoryTemperature?: number | false;
|
|
8
|
+
memoryExtraParams?: Record<string, unknown>;
|
|
9
|
+
autoCaptureMaxIterations?: number;
|
|
10
|
+
autoCaptureIterationTimeout?: number;
|
|
11
|
+
}
|
|
12
|
+
interface ProviderConfigOverrides {
|
|
13
|
+
maxIterations?: number;
|
|
14
|
+
iterationTimeout?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function buildMemoryProviderConfig(config: MemoryProviderRuntimeConfig, overrides?: ProviderConfigOverrides): ProviderConfig;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isPlaceholderApiKey } from "./api-key-placeholder.js";
|
|
2
|
+
export function buildMemoryProviderConfig(config, overrides = {}) {
|
|
3
|
+
const memoryModel = config.memoryModel;
|
|
4
|
+
const memoryApiUrl = config.memoryApiUrl;
|
|
5
|
+
const memoryApiKey = config.memoryApiKey;
|
|
6
|
+
const issues = [];
|
|
7
|
+
// The orcarouter provider presets its own endpoint and default model, so
|
|
8
|
+
// memoryModel / memoryApiUrl are optional there. An API key is always required.
|
|
9
|
+
const isOrcaRouter = config.memoryProvider === "orcarouter";
|
|
10
|
+
if (!memoryModel && !isOrcaRouter)
|
|
11
|
+
issues.push("missing memoryModel");
|
|
12
|
+
if (!memoryApiUrl && !isOrcaRouter)
|
|
13
|
+
issues.push("missing memoryApiUrl");
|
|
14
|
+
if (!memoryApiKey)
|
|
15
|
+
issues.push("missing memoryApiKey");
|
|
16
|
+
if (isPlaceholderApiKey(memoryApiKey))
|
|
17
|
+
issues.push("replace the placeholder memoryApiKey value");
|
|
18
|
+
if (issues.length > 0) {
|
|
19
|
+
throw new Error(`External API not configured for memory provider: ${issues.join("; ")}`);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
model: memoryModel || "",
|
|
23
|
+
apiUrl: memoryApiUrl || "",
|
|
24
|
+
apiKey: memoryApiKey || "",
|
|
25
|
+
memoryTemperature: config.memoryTemperature,
|
|
26
|
+
extraParams: config.memoryExtraParams,
|
|
27
|
+
maxIterations: overrides.maxIterations ?? config.autoCaptureMaxIterations,
|
|
28
|
+
iterationTimeout: overrides.iterationTimeout ?? config.autoCaptureIterationTimeout,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { BaseAIProvider, type ToolCallResult } from "./base-provider.js";
|
|
2
|
+
import { AISessionManager } from "../session/ai-session-manager.js";
|
|
3
|
+
import { type ChatCompletionTool } from "../tools/tool-schema.js";
|
|
4
|
+
import type { AIProviderType } from "../session/session-types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Base implementation for providers that speak the Anthropic Messages API
|
|
7
|
+
* (Anthropic itself plus Anthropic-compatible gateways such as MiniMax).
|
|
8
|
+
*
|
|
9
|
+
* Subclasses override the session provider tag and the resolved endpoint URL so
|
|
10
|
+
* the session store and request routing reflect the upstream provider while the
|
|
11
|
+
* message/tool-call handling stays shared.
|
|
12
|
+
*/
|
|
13
|
+
export declare class AnthropicMessagesProvider extends BaseAIProvider {
|
|
14
|
+
private aiSessionManager;
|
|
15
|
+
constructor(config: any, aiSessionManager: AISessionManager);
|
|
16
|
+
getProviderName(): string;
|
|
17
|
+
supportsSession(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Provider tag stored on AI sessions so a subclass (e.g. MiniMax) records its
|
|
20
|
+
* own tag instead of the literal "anthropic" value.
|
|
21
|
+
*/
|
|
22
|
+
protected sessionProviderTag(): AIProviderType;
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the Messages endpoint URL. The default appends `/messages` to the
|
|
25
|
+
* configured base URL, matching Anthropic's `https://api.anthropic.com/v1`
|
|
26
|
+
* base. Subclasses with a different path layout override this.
|
|
27
|
+
*/
|
|
28
|
+
protected resolveEndpoint(): string;
|
|
29
|
+
protected apiErrorLogLabel(): string;
|
|
30
|
+
protected toolValidationErrorLogLabel(): string;
|
|
31
|
+
protected timeoutLabel(): string;
|
|
32
|
+
executeToolCall(systemPrompt: string, userPrompt: string, toolSchema: ChatCompletionTool, sessionId: string): Promise<ToolCallResult>;
|
|
33
|
+
private extractToolUse;
|
|
34
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { applySafeExtraParams, BaseAIProvider } from "./base-provider.js";
|
|
2
|
+
import { AISessionManager } from "../session/ai-session-manager.js";
|
|
3
|
+
import { ToolSchemaConverter } from "../tools/tool-schema.js";
|
|
4
|
+
import { log } from "../../logger.js";
|
|
5
|
+
import { UserProfileValidator } from "../validators/user-profile-validator.js";
|
|
6
|
+
/**
|
|
7
|
+
* Base implementation for providers that speak the Anthropic Messages API
|
|
8
|
+
* (Anthropic itself plus Anthropic-compatible gateways such as MiniMax).
|
|
9
|
+
*
|
|
10
|
+
* Subclasses override the session provider tag and the resolved endpoint URL so
|
|
11
|
+
* the session store and request routing reflect the upstream provider while the
|
|
12
|
+
* message/tool-call handling stays shared.
|
|
13
|
+
*/
|
|
14
|
+
export class AnthropicMessagesProvider extends BaseAIProvider {
|
|
15
|
+
aiSessionManager;
|
|
16
|
+
constructor(config, aiSessionManager) {
|
|
17
|
+
super(config);
|
|
18
|
+
this.aiSessionManager = aiSessionManager;
|
|
19
|
+
}
|
|
20
|
+
getProviderName() {
|
|
21
|
+
return "anthropic";
|
|
22
|
+
}
|
|
23
|
+
supportsSession() {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Provider tag stored on AI sessions so a subclass (e.g. MiniMax) records its
|
|
28
|
+
* own tag instead of the literal "anthropic" value.
|
|
29
|
+
*/
|
|
30
|
+
sessionProviderTag() {
|
|
31
|
+
return "anthropic";
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the Messages endpoint URL. The default appends `/messages` to the
|
|
35
|
+
* configured base URL, matching Anthropic's `https://api.anthropic.com/v1`
|
|
36
|
+
* base. Subclasses with a different path layout override this.
|
|
37
|
+
*/
|
|
38
|
+
resolveEndpoint() {
|
|
39
|
+
return `${this.config.apiUrl}/messages`;
|
|
40
|
+
}
|
|
41
|
+
apiErrorLogLabel() {
|
|
42
|
+
return "Anthropic Messages API error";
|
|
43
|
+
}
|
|
44
|
+
toolValidationErrorLogLabel() {
|
|
45
|
+
return "Anthropic tool response validation failed";
|
|
46
|
+
}
|
|
47
|
+
timeoutLabel() {
|
|
48
|
+
return "Anthropic API request timeout";
|
|
49
|
+
}
|
|
50
|
+
async executeToolCall(systemPrompt, userPrompt, toolSchema, sessionId) {
|
|
51
|
+
const providerTag = this.sessionProviderTag();
|
|
52
|
+
let session = await this.aiSessionManager.getSession(sessionId, providerTag);
|
|
53
|
+
if (!session) {
|
|
54
|
+
session = await this.aiSessionManager.createSession({
|
|
55
|
+
provider: providerTag,
|
|
56
|
+
sessionId,
|
|
57
|
+
metadata: { systemPrompt },
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const storedMessages = await this.aiSessionManager.getMessages(session.id);
|
|
61
|
+
const messages = [];
|
|
62
|
+
for (const msg of storedMessages) {
|
|
63
|
+
if (msg.role === "system")
|
|
64
|
+
continue;
|
|
65
|
+
const anthropicMsg = {
|
|
66
|
+
role: msg.role,
|
|
67
|
+
content: msg.contentBlocks || msg.content,
|
|
68
|
+
};
|
|
69
|
+
messages.push(anthropicMsg);
|
|
70
|
+
}
|
|
71
|
+
const userSequence = (await this.aiSessionManager.getLastSequence(session.id)) + 1;
|
|
72
|
+
await this.aiSessionManager.addMessage({
|
|
73
|
+
aiSessionId: session.id,
|
|
74
|
+
sequence: userSequence,
|
|
75
|
+
role: "user",
|
|
76
|
+
content: userPrompt,
|
|
77
|
+
});
|
|
78
|
+
messages.push({ role: "user", content: userPrompt });
|
|
79
|
+
let iterations = 0;
|
|
80
|
+
const maxIterations = this.config.maxIterations ?? 5;
|
|
81
|
+
const iterationTimeout = this.config.iterationTimeout ?? 30000;
|
|
82
|
+
while (iterations < maxIterations) {
|
|
83
|
+
iterations++;
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const timeout = setTimeout(() => controller.abort(), iterationTimeout);
|
|
86
|
+
try {
|
|
87
|
+
const tool = ToolSchemaConverter.toAnthropic(toolSchema);
|
|
88
|
+
const requestBody = {
|
|
89
|
+
model: this.config.model,
|
|
90
|
+
max_tokens: this.config.maxTokens ?? 4096,
|
|
91
|
+
system: systemPrompt,
|
|
92
|
+
messages,
|
|
93
|
+
tools: [tool],
|
|
94
|
+
};
|
|
95
|
+
if (this.config.extraParams) {
|
|
96
|
+
applySafeExtraParams(requestBody, this.config.extraParams);
|
|
97
|
+
}
|
|
98
|
+
const headers = {
|
|
99
|
+
"Content-Type": "application/json",
|
|
100
|
+
"anthropic-version": "2023-06-01",
|
|
101
|
+
};
|
|
102
|
+
if (this.config.apiKey) {
|
|
103
|
+
headers["x-api-key"] = this.config.apiKey;
|
|
104
|
+
}
|
|
105
|
+
const response = await fetch(this.resolveEndpoint(), {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers,
|
|
108
|
+
body: JSON.stringify(requestBody),
|
|
109
|
+
signal: controller.signal,
|
|
110
|
+
});
|
|
111
|
+
clearTimeout(timeout);
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
114
|
+
log(this.apiErrorLogLabel(), {
|
|
115
|
+
provider: this.getProviderName(),
|
|
116
|
+
model: this.config.model,
|
|
117
|
+
status: response.status,
|
|
118
|
+
error: errorText,
|
|
119
|
+
iteration: iterations,
|
|
120
|
+
});
|
|
121
|
+
return {
|
|
122
|
+
success: false,
|
|
123
|
+
error: `API error: ${response.status} - ${errorText}`,
|
|
124
|
+
iterations,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const data = (await response.json());
|
|
128
|
+
const assistantSequence = (await this.aiSessionManager.getLastSequence(session.id)) + 1;
|
|
129
|
+
await this.aiSessionManager.addMessage({
|
|
130
|
+
aiSessionId: session.id,
|
|
131
|
+
sequence: assistantSequence,
|
|
132
|
+
role: "assistant",
|
|
133
|
+
content: JSON.stringify(data.content),
|
|
134
|
+
contentBlocks: data.content,
|
|
135
|
+
});
|
|
136
|
+
messages.push({
|
|
137
|
+
role: "assistant",
|
|
138
|
+
content: data.content,
|
|
139
|
+
});
|
|
140
|
+
const toolUse = this.extractToolUse(data, toolSchema.function.name);
|
|
141
|
+
if (toolUse) {
|
|
142
|
+
try {
|
|
143
|
+
const result = UserProfileValidator.validate(toolUse);
|
|
144
|
+
if (!result.valid) {
|
|
145
|
+
throw new Error(result.errors.join(", "));
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
success: true,
|
|
149
|
+
data: result.data,
|
|
150
|
+
iterations,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
catch (validationError) {
|
|
154
|
+
const errorStack = validationError instanceof Error ? validationError.stack : undefined;
|
|
155
|
+
log(this.toolValidationErrorLogLabel(), {
|
|
156
|
+
error: String(validationError),
|
|
157
|
+
stack: errorStack,
|
|
158
|
+
errorType: validationError instanceof Error
|
|
159
|
+
? validationError.constructor.name
|
|
160
|
+
: typeof validationError,
|
|
161
|
+
toolName: toolSchema.function.name,
|
|
162
|
+
iteration: iterations,
|
|
163
|
+
rawData: JSON.stringify(toolUse).slice(0, 500),
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
success: false,
|
|
167
|
+
error: `Validation failed: ${String(validationError)}`,
|
|
168
|
+
iterations,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (data.stop_reason === "end_turn") {
|
|
173
|
+
const retrySequence = (await this.aiSessionManager.getLastSequence(session.id)) + 1;
|
|
174
|
+
const retryPrompt = "Please use the save_memories tool to extract and save the memories from the conversation as instructed.";
|
|
175
|
+
await this.aiSessionManager.addMessage({
|
|
176
|
+
aiSessionId: session.id,
|
|
177
|
+
sequence: retrySequence,
|
|
178
|
+
role: "user",
|
|
179
|
+
content: retryPrompt,
|
|
180
|
+
});
|
|
181
|
+
messages.push({ role: "user", content: retryPrompt });
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
clearTimeout(timeout);
|
|
189
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
190
|
+
return {
|
|
191
|
+
success: false,
|
|
192
|
+
error: `${this.timeoutLabel()} (${this.config.iterationTimeout}ms)`,
|
|
193
|
+
iterations,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
success: false,
|
|
198
|
+
error: String(error),
|
|
199
|
+
iterations,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
error: `Max iterations (${maxIterations}) reached without tool use`,
|
|
206
|
+
iterations,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
extractToolUse(data, expectedToolName) {
|
|
210
|
+
if (!data.content || !Array.isArray(data.content)) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
for (const block of data.content) {
|
|
214
|
+
if (block.type === "tool_use" && block.name === expectedToolName && block.input) {
|
|
215
|
+
return block.input;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface ToolCallResult {
|
|
2
|
+
success: boolean;
|
|
3
|
+
data?: any;
|
|
4
|
+
error?: string;
|
|
5
|
+
iterations?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface ProviderConfig {
|
|
8
|
+
model: string;
|
|
9
|
+
apiUrl: string;
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
maxIterations?: number;
|
|
12
|
+
iterationTimeout?: number;
|
|
13
|
+
maxTokens?: number;
|
|
14
|
+
memoryTemperature?: number | false;
|
|
15
|
+
extraParams?: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
export declare function applySafeExtraParams(requestBody: Record<string, any>, extraParams: Record<string, unknown>): void;
|
|
18
|
+
export declare abstract class BaseAIProvider {
|
|
19
|
+
protected config: ProviderConfig;
|
|
20
|
+
constructor(config: ProviderConfig);
|
|
21
|
+
abstract executeToolCall(systemPrompt: string, userPrompt: string, toolSchema: any, sessionId: string): Promise<ToolCallResult>;
|
|
22
|
+
abstract getProviderName(): string;
|
|
23
|
+
abstract supportsSession(): boolean;
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const PROTECTED_KEYS = new Set([
|
|
2
|
+
"model",
|
|
3
|
+
"messages",
|
|
4
|
+
"tools",
|
|
5
|
+
"tool_choice",
|
|
6
|
+
"temperature",
|
|
7
|
+
"input",
|
|
8
|
+
"instructions",
|
|
9
|
+
"conversation",
|
|
10
|
+
"stream",
|
|
11
|
+
]);
|
|
12
|
+
export function applySafeExtraParams(requestBody, extraParams) {
|
|
13
|
+
for (const [key, value] of Object.entries(extraParams)) {
|
|
14
|
+
if (!PROTECTED_KEYS.has(key)) {
|
|
15
|
+
requestBody[key] = value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class BaseAIProvider {
|
|
20
|
+
config;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.config = config;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseAIProvider, type ToolCallResult } from "./base-provider.js";
|
|
2
|
+
import { AISessionManager } from "../session/ai-session-manager.js";
|
|
3
|
+
import type { ChatCompletionTool } from "../tools/tool-schema.js";
|
|
4
|
+
/**
|
|
5
|
+
* Google Gemini Provider
|
|
6
|
+
* Supports Google's Gemini models (e.g. gemini-1.5-flash) via Google AI Studio API.
|
|
7
|
+
*/
|
|
8
|
+
export declare class GoogleGeminiProvider extends BaseAIProvider {
|
|
9
|
+
private aiSessionManager;
|
|
10
|
+
constructor(config: any, aiSessionManager: AISessionManager);
|
|
11
|
+
getProviderName(): string;
|
|
12
|
+
supportsSession(): boolean;
|
|
13
|
+
private addToolResponse;
|
|
14
|
+
executeToolCall(systemPrompt: string, userPrompt: string, toolSchema: ChatCompletionTool, sessionId: string): Promise<ToolCallResult>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { BaseAIProvider } from "./base-provider.js";
|
|
2
|
+
import { AISessionManager } from "../session/ai-session-manager.js";
|
|
3
|
+
import { log } from "../../logger.js";
|
|
4
|
+
import { UserProfileValidator } from "../validators/user-profile-validator.js";
|
|
5
|
+
/**
|
|
6
|
+
* Google Gemini Provider
|
|
7
|
+
* Supports Google's Gemini models (e.g. gemini-1.5-flash) via Google AI Studio API.
|
|
8
|
+
*/
|
|
9
|
+
export class GoogleGeminiProvider extends BaseAIProvider {
|
|
10
|
+
aiSessionManager;
|
|
11
|
+
constructor(config, aiSessionManager) {
|
|
12
|
+
super(config);
|
|
13
|
+
this.aiSessionManager = aiSessionManager;
|
|
14
|
+
}
|
|
15
|
+
getProviderName() {
|
|
16
|
+
return "google-gemini";
|
|
17
|
+
}
|
|
18
|
+
supportsSession() {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
async addToolResponse(sessionId, messages, toolCallId, content) {
|
|
22
|
+
const sequence = (await this.aiSessionManager.getLastSequence(sessionId)) + 1;
|
|
23
|
+
await this.aiSessionManager.addMessage({
|
|
24
|
+
aiSessionId: sessionId,
|
|
25
|
+
sequence,
|
|
26
|
+
role: "tool",
|
|
27
|
+
content,
|
|
28
|
+
toolCallId,
|
|
29
|
+
});
|
|
30
|
+
// Gemini tool response format
|
|
31
|
+
messages.push({
|
|
32
|
+
role: "function",
|
|
33
|
+
parts: [
|
|
34
|
+
{
|
|
35
|
+
functionResponse: {
|
|
36
|
+
name: toolCallId.split(":")[0], // Gemini expects the name of the function
|
|
37
|
+
response: JSON.parse(content),
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async executeToolCall(systemPrompt, userPrompt, toolSchema, sessionId) {
|
|
44
|
+
let session = await this.aiSessionManager.getSession(sessionId, "google-gemini");
|
|
45
|
+
if (!session) {
|
|
46
|
+
session = await this.aiSessionManager.createSession({
|
|
47
|
+
provider: "google-gemini",
|
|
48
|
+
sessionId,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const existingMessages = await this.aiSessionManager.getMessages(session.id);
|
|
52
|
+
const contents = [];
|
|
53
|
+
// System instruction is separate in Gemini API
|
|
54
|
+
const geminiSystemInstruction = {
|
|
55
|
+
parts: [{ text: systemPrompt }],
|
|
56
|
+
};
|
|
57
|
+
// Convert existing messages to Gemini format
|
|
58
|
+
for (const msg of existingMessages) {
|
|
59
|
+
if (msg.role === "system")
|
|
60
|
+
continue; // Skip system as it's passed separately
|
|
61
|
+
const role = msg.role === "assistant" ? "model" : "user";
|
|
62
|
+
const parts = [];
|
|
63
|
+
if (msg.content) {
|
|
64
|
+
parts.push({ text: msg.content });
|
|
65
|
+
}
|
|
66
|
+
if (msg.toolCalls) {
|
|
67
|
+
for (const tc of msg.toolCalls) {
|
|
68
|
+
parts.push({
|
|
69
|
+
functionCall: {
|
|
70
|
+
name: tc.function.name,
|
|
71
|
+
args: JSON.parse(tc.function.arguments),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (msg.role === "tool") {
|
|
77
|
+
contents.push({
|
|
78
|
+
role: "function",
|
|
79
|
+
parts: [
|
|
80
|
+
{
|
|
81
|
+
functionResponse: {
|
|
82
|
+
name: (msg.toolCallId || "").split(":")[0],
|
|
83
|
+
response: JSON.parse(msg.content),
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
contents.push({ role, parts });
|
|
91
|
+
}
|
|
92
|
+
if (contents.length === 0 || contents[contents.length - 1].role !== "user") {
|
|
93
|
+
const userSequence = (await this.aiSessionManager.getLastSequence(session.id)) + 1;
|
|
94
|
+
await this.aiSessionManager.addMessage({
|
|
95
|
+
aiSessionId: session.id,
|
|
96
|
+
sequence: userSequence,
|
|
97
|
+
role: "user",
|
|
98
|
+
content: userPrompt,
|
|
99
|
+
});
|
|
100
|
+
contents.push({ role: "user", parts: [{ text: userPrompt }] });
|
|
101
|
+
}
|
|
102
|
+
let iterations = 0;
|
|
103
|
+
const maxIterations = this.config.maxIterations ?? 5;
|
|
104
|
+
const iterationTimeout = this.config.iterationTimeout ?? 30000;
|
|
105
|
+
// Gemini API expects the tool name as a function declaration
|
|
106
|
+
const tools = [
|
|
107
|
+
{
|
|
108
|
+
functionDeclarations: [
|
|
109
|
+
{
|
|
110
|
+
name: toolSchema.function.name,
|
|
111
|
+
description: toolSchema.function.description,
|
|
112
|
+
parameters: toolSchema.function.parameters,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
while (iterations < maxIterations) {
|
|
118
|
+
iterations++;
|
|
119
|
+
const controller = new AbortController();
|
|
120
|
+
const timeout = setTimeout(() => controller.abort(), iterationTimeout);
|
|
121
|
+
try {
|
|
122
|
+
const baseUrl = this.config.apiUrl || "https://generativelanguage.googleapis.com/v1beta";
|
|
123
|
+
const url = `${baseUrl}/models/${this.config.model}:generateContent?key=${this.config.apiKey}`;
|
|
124
|
+
const requestBody = {
|
|
125
|
+
contents,
|
|
126
|
+
systemInstruction: geminiSystemInstruction,
|
|
127
|
+
tools,
|
|
128
|
+
toolConfig: {
|
|
129
|
+
functionCallingConfig: {
|
|
130
|
+
mode: "ANY", // Force function calling
|
|
131
|
+
allowedFunctionNames: [toolSchema.function.name],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
generationConfig: {
|
|
135
|
+
temperature: this.config.memoryTemperature ?? 0.3,
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
const response = await fetch(url, {
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers: { "Content-Type": "application/json" },
|
|
141
|
+
body: JSON.stringify(requestBody),
|
|
142
|
+
signal: controller.signal,
|
|
143
|
+
});
|
|
144
|
+
clearTimeout(timeout);
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
147
|
+
log("Gemini API error", {
|
|
148
|
+
provider: this.getProviderName(),
|
|
149
|
+
model: this.config.model,
|
|
150
|
+
status: response.status,
|
|
151
|
+
error: errorText,
|
|
152
|
+
iteration: iterations,
|
|
153
|
+
});
|
|
154
|
+
return {
|
|
155
|
+
success: false,
|
|
156
|
+
error: `Gemini API error: ${response.status} - ${errorText}`,
|
|
157
|
+
iterations,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const data = (await response.json());
|
|
161
|
+
const candidate = data.candidates?.[0];
|
|
162
|
+
if (!candidate || !candidate.content) {
|
|
163
|
+
return { success: false, error: "Invalid Gemini API response format", iterations };
|
|
164
|
+
}
|
|
165
|
+
const modelMsg = candidate.content;
|
|
166
|
+
const assistantSequence = (await this.aiSessionManager.getLastSequence(session.id)) + 1;
|
|
167
|
+
// Map Gemini response back to our internal message format
|
|
168
|
+
const assistantMsg = {
|
|
169
|
+
aiSessionId: session.id,
|
|
170
|
+
sequence: assistantSequence,
|
|
171
|
+
role: "assistant",
|
|
172
|
+
content: "",
|
|
173
|
+
toolCalls: [],
|
|
174
|
+
};
|
|
175
|
+
for (const part of modelMsg.parts) {
|
|
176
|
+
if (part.text)
|
|
177
|
+
assistantMsg.content += part.text;
|
|
178
|
+
if (part.functionCall) {
|
|
179
|
+
assistantMsg.toolCalls.push({
|
|
180
|
+
id: `${part.functionCall.name}:${Date.now()}`,
|
|
181
|
+
type: "function",
|
|
182
|
+
function: {
|
|
183
|
+
name: part.functionCall.name,
|
|
184
|
+
arguments: JSON.stringify(part.functionCall.args),
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
await this.aiSessionManager.addMessage(assistantMsg);
|
|
190
|
+
contents.push(modelMsg);
|
|
191
|
+
if (assistantMsg.toolCalls.length > 0) {
|
|
192
|
+
for (const toolCall of assistantMsg.toolCalls) {
|
|
193
|
+
if (toolCall.function.name === toolSchema.function.name) {
|
|
194
|
+
try {
|
|
195
|
+
const parsed = JSON.parse(toolCall.function.arguments);
|
|
196
|
+
const result = UserProfileValidator.validate(parsed);
|
|
197
|
+
if (!result.valid)
|
|
198
|
+
throw new Error(result.errors.join(", "));
|
|
199
|
+
await this.addToolResponse(session.id, contents, toolCall.id, JSON.stringify({ success: true }));
|
|
200
|
+
return { success: true, data: result.data, iterations };
|
|
201
|
+
}
|
|
202
|
+
catch (validationError) {
|
|
203
|
+
const errorMessage = `Validation failed: ${String(validationError)}`;
|
|
204
|
+
await this.addToolResponse(session.id, contents, toolCall.id, JSON.stringify({ success: false, error: errorMessage }));
|
|
205
|
+
return { success: false, error: errorMessage, iterations };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Retry if no tool call was made
|
|
211
|
+
const retryPrompt = "Please use the save_memories tool as instructed.";
|
|
212
|
+
const retrySequence = (await this.aiSessionManager.getLastSequence(session.id)) + 1;
|
|
213
|
+
await this.aiSessionManager.addMessage({
|
|
214
|
+
aiSessionId: session.id,
|
|
215
|
+
sequence: retrySequence,
|
|
216
|
+
role: "user",
|
|
217
|
+
content: retryPrompt,
|
|
218
|
+
});
|
|
219
|
+
contents.push({ role: "user", parts: [{ text: retryPrompt }] });
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
clearTimeout(timeout);
|
|
223
|
+
return { success: false, error: String(error), iterations };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return { success: false, error: `Max iterations (${maxIterations}) reached`, iterations };
|
|
227
|
+
}
|
|
228
|
+
}
|