feat: add all major features - Venice web UI, CLI tools, web-ui enhancements
Some checks are pending
CI / build-check-test (push) Waiting to run
Some checks are pending
CI / build-check-test (push) Waiting to run
CLI (coding-agent): - web-search.ts: DuckDuckGo web search tool - web-fetch.ts: Fetch and read web pages - image-gen.ts: Venice AI image generation - memory.ts: In-session memory store/recall - browser.ts: Playwright headless browser tool - tools/index.ts: Register all new tools - model-resolver.ts: Venice as default provider Web UI: - VeniceModelBrowser.ts: Model picker with category tags - ProvidersModelsTab.ts: Venice API key + model browser - ProviderKeyInput.ts: Venice key validation - ModelSelector.ts: Updated model selector - SettingsDialog.ts: Settings wired up - tools/index.ts: Export new tools - utils/model-discovery.ts: Venice model fetching - utils/format.ts: Formatting helpers - example/main.ts: Wire up new tools in example app jae-ai: - env-api-keys.ts: VENICE_API_KEY mapping - types.ts: venice in KnownProvider - oauth/venice.ts: Venice OAuth/API key provider - oauth/index.ts: Register Venice provider
This commit is contained in:
parent
29574c7c86
commit
903540fa95
30 changed files with 709 additions and 76 deletions
|
|
@ -126,7 +126,7 @@ export function getEnvApiKey(provider: any): string | undefined {
|
|||
opencode: "OPENCODE_API_KEY",
|
||||
"opencode-go": "OPENCODE_API_KEY",
|
||||
"kimi-coding": "KIMI_API_KEY",
|
||||
venice: "VENICE_API_KEY",
|
||||
venice: "VENICE_API_KEY",
|
||||
};
|
||||
|
||||
const envVar = envMap[provider];
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export type KnownProvider =
|
|||
| "opencode"
|
||||
| "opencode-go"
|
||||
| "kimi-coding"
|
||||
| "venice";
|
||||
| "venice";
|
||||
export type Provider = KnownProvider | string;
|
||||
|
||||
export type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh";
|
||||
|
|
@ -90,8 +90,8 @@ export interface StreamOptions {
|
|||
* Not supported by all providers (e.g., AWS Bedrock uses SDK auth).
|
||||
*/
|
||||
headers?: Record<string, string>;
|
||||
/** Capability tags (e.g. 'tools', 'vision', 'reasoning', 'code', 'image-generation', etc.) */
|
||||
tags?: string[];
|
||||
/** Capability tags (e.g. 'tools', 'vision', 'reasoning', 'code', 'image-generation', etc.) */
|
||||
tags?: string[];
|
||||
/**
|
||||
* Maximum delay in milliseconds to wait for a retry when the server requests a long wait.
|
||||
* If the server's requested delay exceeds this value, the request fails immediately
|
||||
|
|
@ -331,8 +331,8 @@ export interface Model<TApi extends Api> {
|
|||
contextWindow: number;
|
||||
maxTokens: number;
|
||||
headers?: Record<string, string>;
|
||||
/** Capability tags (e.g. 'tools', 'vision', 'reasoning', 'code', 'image-generation', etc.) */
|
||||
tags?: string[];
|
||||
/** Capability tags (e.g. 'tools', 'vision', 'reasoning', 'code', 'image-generation', etc.) */
|
||||
tags?: string[];
|
||||
/** Compatibility overrides for OpenAI-compatible APIs. If not set, auto-detected from baseUrl. */
|
||||
compat?: TApi extends "openai-completions"
|
||||
? OpenAICompletionsCompat
|
||||
|
|
|
|||
|
|
@ -25,10 +25,9 @@ export { antigravityOAuthProvider, loginAntigravity, refreshAntigravityToken } f
|
|||
export { geminiCliOAuthProvider, loginGeminiCli, refreshGoogleCloudToken } from "./google-gemini-cli.js";
|
||||
// OpenAI Codex (ChatGPT OAuth)
|
||||
export { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from "./openai-codex.js";
|
||||
// Venice AI (API Key)
|
||||
export { loginVenice, veniceOAuthProvider, refreshVeniceToken } from "./venice.js";
|
||||
|
||||
export * from "./types.js";
|
||||
// Venice AI (API Key)
|
||||
export { loginVenice, refreshVeniceToken, veniceOAuthProvider } from "./venice.js";
|
||||
|
||||
// ============================================================================
|
||||
// Provider Registry
|
||||
|
|
@ -39,8 +38,8 @@ import { githubCopilotOAuthProvider } from "./github-copilot.js";
|
|||
import { antigravityOAuthProvider } from "./google-antigravity.js";
|
||||
import { geminiCliOAuthProvider } from "./google-gemini-cli.js";
|
||||
import { openaiCodexOAuthProvider } from "./openai-codex.js";
|
||||
import { veniceOAuthProvider } from "./venice.js";
|
||||
import type { OAuthCredentials, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface } from "./types.js";
|
||||
import { veniceOAuthProvider } from "./venice.js";
|
||||
|
||||
const BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [
|
||||
anthropicOAuthProvider,
|
||||
|
|
@ -48,7 +47,7 @@ const BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [
|
|||
geminiCliOAuthProvider,
|
||||
antigravityOAuthProvider,
|
||||
openaiCodexOAuthProvider,
|
||||
veniceOAuthProvider,
|
||||
veniceOAuthProvider,
|
||||
];
|
||||
|
||||
const oauthProviderRegistry = new Map<string, OAuthProviderInterface>(
|
||||
|
|
|
|||
|
|
@ -9,46 +9,46 @@ import type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } fr
|
|||
* Get your API key at: https://venice.ai/settings/api
|
||||
*/
|
||||
export async function loginVenice(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
||||
callbacks.onProgress?.("To get an API key, visit https://venice.ai/settings/api");
|
||||
callbacks.onProgress?.("To get an API key, visit https://venice.ai/settings/api");
|
||||
|
||||
const apiKey = await callbacks.onPrompt({
|
||||
message: "Enter your Venice AI API key:",
|
||||
placeholder: "e.g. your_venice_api_key",
|
||||
});
|
||||
const apiKey = await callbacks.onPrompt({
|
||||
message: "Enter your Venice AI API key:",
|
||||
placeholder: "e.g. your_venice_api_key",
|
||||
});
|
||||
|
||||
if (!apiKey || !apiKey.trim()) {
|
||||
throw new Error("No API key provided");
|
||||
}
|
||||
if (!apiKey || !apiKey.trim()) {
|
||||
throw new Error("No API key provided");
|
||||
}
|
||||
|
||||
return {
|
||||
access: apiKey.trim(),
|
||||
refresh: "",
|
||||
// API keys don't expire — set a 10-year window so the refresh path is never hit
|
||||
expires: Date.now() + 1000 * 60 * 60 * 24 * 365 * 10,
|
||||
};
|
||||
return {
|
||||
access: apiKey.trim(),
|
||||
refresh: "",
|
||||
// API keys don't expire — set a 10-year window so the refresh path is never hit
|
||||
expires: Date.now() + 1000 * 60 * 60 * 24 * 365 * 10,
|
||||
};
|
||||
}
|
||||
|
||||
export async function refreshVeniceToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
|
||||
// API keys don't expire, just bump the expiry forward
|
||||
return {
|
||||
...credentials,
|
||||
expires: Date.now() + 1000 * 60 * 60 * 24 * 365 * 10,
|
||||
};
|
||||
// API keys don't expire, just bump the expiry forward
|
||||
return {
|
||||
...credentials,
|
||||
expires: Date.now() + 1000 * 60 * 60 * 24 * 365 * 10,
|
||||
};
|
||||
}
|
||||
|
||||
export const veniceOAuthProvider: OAuthProviderInterface = {
|
||||
id: "venice",
|
||||
name: "Venice AI (API Key)",
|
||||
id: "venice",
|
||||
name: "Venice AI (API Key)",
|
||||
|
||||
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
||||
return loginVenice(callbacks);
|
||||
},
|
||||
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
||||
return loginVenice(callbacks);
|
||||
},
|
||||
|
||||
async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
|
||||
return refreshVeniceToken(credentials);
|
||||
},
|
||||
async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
|
||||
return refreshVeniceToken(credentials);
|
||||
},
|
||||
|
||||
getApiKey(credentials: OAuthCredentials): string {
|
||||
return credentials.access;
|
||||
},
|
||||
getApiKey(credentials: OAuthCredentials): string {
|
||||
return credentials.access;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,14 +15,7 @@
|
|||
|
||||
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { basename, dirname, join, resolve } from "node:path";
|
||||
import type {
|
||||
Agent,
|
||||
AgentEvent,
|
||||
AgentMessage,
|
||||
AgentState,
|
||||
AgentTool,
|
||||
ThinkingLevel,
|
||||
} from "@jaeswift/jae-agent-core";
|
||||
import type { Agent, AgentEvent, AgentMessage, AgentState, AgentTool, ThinkingLevel } from "@jaeswift/jae-agent-core";
|
||||
import type { AssistantMessage, ImageContent, Message, Model, TextContent } from "@jaeswift/jae-ai";
|
||||
import { isContextOverflow, modelsAreEqual, resetApiProviders, supportsXhigh } from "@jaeswift/jae-ai";
|
||||
import { getDocsPath } from "../config.js";
|
||||
|
|
|
|||
|
|
@ -6,12 +6,7 @@
|
|||
* try to refresh tokens simultaneously.
|
||||
*/
|
||||
|
||||
import {
|
||||
getEnvApiKey,
|
||||
type OAuthCredentials,
|
||||
type OAuthLoginCallbacks,
|
||||
type OAuthProviderId,
|
||||
} from "@jaeswift/jae-ai";
|
||||
import { getEnvApiKey, type OAuthCredentials, type OAuthLoginCallbacks, type OAuthProviderId } from "@jaeswift/jae-ai";
|
||||
import { getOAuthApiKey, getOAuthProvider, getOAuthProviders } from "@jaeswift/jae-ai/oauth";
|
||||
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ import { createRequire } from "node:module";
|
|||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createJiti } from "@mariozechner/jiti";
|
||||
import * as _bundledPiAgentCore from "@jaeswift/jae-agent-core";
|
||||
import * as _bundledPiAi from "@jaeswift/jae-ai";
|
||||
import * as _bundledPiAiOauth from "@jaeswift/jae-ai/oauth";
|
||||
import type { KeyId } from "@jaeswift/jae-tui";
|
||||
import * as _bundledPiTui from "@jaeswift/jae-tui";
|
||||
import { createJiti } from "@mariozechner/jiti";
|
||||
// Static imports of packages that extensions may use.
|
||||
// These MUST be static so Bun bundles them into the compiled binary.
|
||||
// The virtualModules option then makes them available to extensions.
|
||||
|
|
|
|||
|
|
@ -8,12 +8,7 @@
|
|||
* - Interact with the user via UI primitives
|
||||
*/
|
||||
|
||||
import type {
|
||||
AgentMessage,
|
||||
AgentToolResult,
|
||||
AgentToolUpdateCallback,
|
||||
ThinkingLevel,
|
||||
} from "@jaeswift/jae-agent-core";
|
||||
import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback, ThinkingLevel } from "@jaeswift/jae-agent-core";
|
||||
import type {
|
||||
Api,
|
||||
AssistantMessageEvent,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export const defaultModelPerProvider: Record<KnownProvider, string> = {
|
|||
opencode: "claude-opus-4-6",
|
||||
"opencode-go": "kimi-k2.5",
|
||||
"kimi-coding": "kimi-k2-thinking",
|
||||
venice: "llama-3.3-70b",
|
||||
venice: "llama-3.3-70b",
|
||||
};
|
||||
|
||||
export interface ScopedModel {
|
||||
|
|
|
|||
126
packages/coding-agent/src/core/tools/browser.ts
Normal file
126
packages/coding-agent/src/core/tools/browser.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
|
||||
import { writeFileSync, mkdirSync, existsSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import type { AgentTool } from "@jaeswift/jae-agent-core";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
|
||||
const browserSchema = Type.Object({
|
||||
action: Type.Union([
|
||||
Type.Literal("navigate"),
|
||||
Type.Literal("screenshot"),
|
||||
Type.Literal("click"),
|
||||
Type.Literal("type"),
|
||||
Type.Literal("content"),
|
||||
Type.Literal("close"),
|
||||
], { description: "navigate: go to URL | screenshot: capture page | click: click element | type: type into element | content: get page text | close: close browser" }),
|
||||
url: Type.Optional(Type.String({ description: "URL to navigate to (for navigate action)" })),
|
||||
selector: Type.Optional(Type.String({ description: "CSS selector (for click/type actions)" })),
|
||||
text: Type.Optional(Type.String({ description: "Text to type (for type action)" })),
|
||||
wait: Type.Optional(Type.Number({ description: "Milliseconds to wait after action (default: 1000)" })),
|
||||
});
|
||||
|
||||
export type BrowserToolInput = Static<typeof browserSchema>;
|
||||
|
||||
export interface BrowserToolDetails {
|
||||
action: string;
|
||||
url?: string;
|
||||
screenshotPath?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
let _playwright: any = null;
|
||||
let _browser: any = null;
|
||||
let _page: any = null;
|
||||
|
||||
async function getPlaywright() {
|
||||
if (!_playwright) {
|
||||
try {
|
||||
const { chromium } = await import("playwright");
|
||||
_playwright = chromium;
|
||||
} catch {
|
||||
throw new Error("Playwright not installed. Run: npm install -g playwright && npx playwright install chromium");
|
||||
}
|
||||
}
|
||||
return _playwright;
|
||||
}
|
||||
|
||||
async function getPage() {
|
||||
const pw = await getPlaywright();
|
||||
if (!_browser) _browser = await pw.launch({ headless: true });
|
||||
if (!_page) _page = await _browser.newPage();
|
||||
return _page;
|
||||
}
|
||||
|
||||
export const browserTool: AgentTool<typeof browserSchema, BrowserToolDetails> = {
|
||||
name: "browser",
|
||||
label: "Browser",
|
||||
description: "Control a headless Chromium browser. Navigate pages, take screenshots, click elements, type text, and extract page content. Requires playwright.",
|
||||
parameters: browserSchema,
|
||||
async execute(toolCallId, params, signal) {
|
||||
const { action, url, selector, text, wait = 1000 } = params;
|
||||
|
||||
try {
|
||||
if (action === "close") {
|
||||
if (_browser) { await _browser.close(); _browser = null; _page = null; }
|
||||
return { content: [{ type: "text", text: "Browser closed." }], details: { action } };
|
||||
}
|
||||
|
||||
const page = await getPage();
|
||||
|
||||
if (action === "navigate") {
|
||||
if (!url) return { content: [{ type: "text", text: "Error: url required for navigate" }], details: { action } };
|
||||
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 30000 });
|
||||
await page.waitForTimeout(wait);
|
||||
const title = await page.title();
|
||||
return {
|
||||
content: [{ type: "text", text: `Navigated to: ${url}\nPage title: ${title}` }],
|
||||
details: { action, url },
|
||||
};
|
||||
}
|
||||
|
||||
if (action === "screenshot") {
|
||||
const dir = join(tmpdir(), "jae-browser");
|
||||
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
||||
const path = join(dir, `screenshot-${Date.now()}.png`);
|
||||
await page.screenshot({ path, fullPage: false });
|
||||
const currentUrl = page.url();
|
||||
return {
|
||||
content: [{ type: "text", text: `Screenshot saved to: ${path}\nCurrent URL: ${currentUrl}` }],
|
||||
details: { action, url: currentUrl, screenshotPath: path },
|
||||
};
|
||||
}
|
||||
|
||||
if (action === "content") {
|
||||
const content = await page.evaluate(() => document.body.innerText);
|
||||
const truncated = content.slice(0, 8000);
|
||||
return {
|
||||
content: [{ type: "text", text: truncated + (content.length > 8000 ? "\n...[truncated]" : "") }],
|
||||
details: { action, url: page.url() },
|
||||
};
|
||||
}
|
||||
|
||||
if (action === "click") {
|
||||
if (!selector) return { content: [{ type: "text", text: "Error: selector required for click" }], details: { action } };
|
||||
await page.click(selector, { timeout: 10000 });
|
||||
await page.waitForTimeout(wait);
|
||||
return { content: [{ type: "text", text: `Clicked: ${selector}` }], details: { action } };
|
||||
}
|
||||
|
||||
if (action === "type") {
|
||||
if (!selector) return { content: [{ type: "text", text: "Error: selector required for type" }], details: { action } };
|
||||
if (!text) return { content: [{ type: "text", text: "Error: text required for type" }], details: { action } };
|
||||
await page.fill(selector, text);
|
||||
await page.waitForTimeout(wait);
|
||||
return { content: [{ type: "text", text: `Typed into: ${selector}` }], details: { action } };
|
||||
}
|
||||
|
||||
return { content: [{ type: "text", text: `Unknown action: ${action}` }], details: { action } };
|
||||
} catch (err: any) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Browser error: ${err.message}` }],
|
||||
details: { action, error: err.message },
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
96
packages/coding-agent/src/core/tools/image-gen.ts
Normal file
96
packages/coding-agent/src/core/tools/image-gen.ts
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
|
||||
import { writeFileSync, mkdirSync, existsSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import type { AgentTool } from "@jaeswift/jae-agent-core";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
|
||||
const imageGenSchema = Type.Object({
|
||||
prompt: Type.String({ description: "Image generation prompt" }),
|
||||
model: Type.Optional(Type.String({ description: "Venice image model (default: fluently-xl)" })),
|
||||
width: Type.Optional(Type.Number({ description: "Width in pixels (default: 1024)" })),
|
||||
height: Type.Optional(Type.Number({ description: "Height in pixels (default: 1024)" })),
|
||||
steps: Type.Optional(Type.Number({ description: "Inference steps (default: 20)" })),
|
||||
output_dir: Type.Optional(Type.String({ description: "Directory to save image (default: ./images)" })),
|
||||
});
|
||||
|
||||
export type ImageGenToolInput = Static<typeof imageGenSchema>;
|
||||
|
||||
export interface ImageGenToolDetails {
|
||||
path: string;
|
||||
model: string;
|
||||
prompt: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export const imageGenTool: AgentTool<typeof imageGenSchema, ImageGenToolDetails> = {
|
||||
name: "generate_image",
|
||||
label: "Generate Image",
|
||||
description: "Generate an image using Venice AI image models and save it to disk. Uses VENICE_API_KEY or OPENAI_API_KEY env var.",
|
||||
parameters: imageGenSchema,
|
||||
async execute(toolCallId, params, signal) {
|
||||
const {
|
||||
prompt,
|
||||
model = "fluently-xl",
|
||||
width = 1024,
|
||||
height = 1024,
|
||||
steps = 20,
|
||||
output_dir = "./images",
|
||||
} = params;
|
||||
|
||||
const apiKey = process.env.VENICE_API_KEY || process.env.OPENAI_API_KEY;
|
||||
if (!apiKey) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Error: VENICE_API_KEY or OPENAI_API_KEY environment variable not set." }],
|
||||
details: { path: "", model, prompt, width, height },
|
||||
};
|
||||
}
|
||||
|
||||
const body = JSON.stringify({
|
||||
model,
|
||||
prompt,
|
||||
width,
|
||||
height,
|
||||
steps,
|
||||
return_binary: false,
|
||||
safe_mode: false,
|
||||
});
|
||||
|
||||
const res = await fetch("https://api.venice.ai/api/v1/image/generate", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${apiKey}`,
|
||||
},
|
||||
body,
|
||||
signal: signal ?? AbortSignal.timeout(60000),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const err = await res.text();
|
||||
return {
|
||||
content: [{ type: "text", text: `Image generation failed (${res.status}): ${err}` }],
|
||||
details: { path: "", model, prompt, width, height },
|
||||
};
|
||||
}
|
||||
|
||||
const data = await res.json() as any;
|
||||
const b64 = data?.images?.[0];
|
||||
if (!b64) {
|
||||
return {
|
||||
content: [{ type: "text", text: "No image returned from Venice API." }],
|
||||
details: { path: "", model, prompt, width, height },
|
||||
};
|
||||
}
|
||||
|
||||
if (!existsSync(output_dir)) mkdirSync(output_dir, { recursive: true });
|
||||
const filename = `gen-${Date.now()}.png`;
|
||||
const filepath = join(output_dir, filename);
|
||||
writeFileSync(filepath, Buffer.from(b64, "base64"));
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: `Image saved to: ${filepath}\nModel: ${model}\nPrompt: ${prompt}\nSize: ${width}x${height}` }],
|
||||
details: { path: filepath, model, prompt, width, height },
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
@ -107,7 +107,13 @@ import { createWriteTool, createWriteToolDefinition, writeTool, writeToolDefinit
|
|||
export type Tool = AgentTool<any>;
|
||||
export type ToolDef = ToolDefinition<any, any>;
|
||||
|
||||
export const codingTools: Tool[] = [readTool, bashTool, editTool, writeTool];
|
||||
|
||||
import { webSearchTool } from "./web-search.js";
|
||||
import { webFetchTool } from "./web-fetch.js";
|
||||
import { imageGenTool } from "./image-gen.js";
|
||||
import { memoryTool } from "./memory.js";
|
||||
import { browserTool } from "./browser.js";
|
||||
export const codingTools: Tool[] = [readTool, bashTool, editTool, writeTool, webSearchTool, webFetchTool, imageGenTool, memoryTool, browserTool];
|
||||
export const readOnlyTools: Tool[] = [readTool, grepTool, findTool, lsTool];
|
||||
|
||||
export const allTools = {
|
||||
|
|
@ -191,3 +197,10 @@ export function createAllTools(cwd: string, options?: ToolsOptions): Record<Tool
|
|||
ls: createLsTool(cwd),
|
||||
};
|
||||
}
|
||||
|
||||
// ── New tools ────────────────────────────────────────────────────────────────
|
||||
export { webSearchTool, type WebSearchToolInput, type WebSearchToolDetails, type WebSearchResult } from "./web-search.js";
|
||||
export { webFetchTool, type WebFetchToolInput, type WebFetchToolDetails } from "./web-fetch.js";
|
||||
export { imageGenTool, type ImageGenToolInput, type ImageGenToolDetails } from "./image-gen.js";
|
||||
export { memoryTool, type MemoryToolInput, type MemoryToolDetails } from "./memory.js";
|
||||
export { browserTool, type BrowserToolInput, type BrowserToolDetails } from "./browser.js";
|
||||
|
|
|
|||
112
packages/coding-agent/src/core/tools/memory.ts
Normal file
112
packages/coding-agent/src/core/tools/memory.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { AgentTool } from "@jaeswift/jae-agent-core";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
|
||||
const MEMORY_DIR = join(homedir(), ".jae", "memory");
|
||||
const MEMORY_FILE = join(MEMORY_DIR, "memories.json");
|
||||
|
||||
interface MemoryEntry {
|
||||
id: string;
|
||||
content: string;
|
||||
tags: string[];
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
function loadMemories(): MemoryEntry[] {
|
||||
if (!existsSync(MEMORY_FILE)) return [];
|
||||
try {
|
||||
return JSON.parse(readFileSync(MEMORY_FILE, "utf-8")) as MemoryEntry[];
|
||||
} catch { return []; }
|
||||
}
|
||||
|
||||
function saveMemories(entries: MemoryEntry[]): void {
|
||||
if (!existsSync(MEMORY_DIR)) mkdirSync(MEMORY_DIR, { recursive: true });
|
||||
writeFileSync(MEMORY_FILE, JSON.stringify(entries, null, 2));
|
||||
}
|
||||
|
||||
function scoreMatch(entry: MemoryEntry, query: string): number {
|
||||
const q = query.toLowerCase();
|
||||
const text = (entry.content + " " + entry.tags.join(" ")).toLowerCase();
|
||||
const words = q.split(/\s+/);
|
||||
return words.filter(w => text.includes(w)).length / Math.max(words.length, 1);
|
||||
}
|
||||
|
||||
const memorySchema = Type.Object({
|
||||
action: Type.Union([
|
||||
Type.Literal("save"),
|
||||
Type.Literal("recall"),
|
||||
Type.Literal("list"),
|
||||
Type.Literal("delete"),
|
||||
], { description: "save: store info | recall: search by query | list: show all | delete: remove by id" }),
|
||||
content: Type.Optional(Type.String({ description: "Content to save (required for save action)" })),
|
||||
query: Type.Optional(Type.String({ description: "Search query (required for recall action)" })),
|
||||
tags: Type.Optional(Type.Array(Type.String(), { description: "Tags for categorisation (optional for save)" })),
|
||||
id: Type.Optional(Type.String({ description: "Memory ID (required for delete action)" })),
|
||||
limit: Type.Optional(Type.Number({ description: "Max results to return for recall (default: 5)" })),
|
||||
});
|
||||
|
||||
export type MemoryToolInput = Static<typeof memorySchema>;
|
||||
|
||||
export interface MemoryToolDetails {
|
||||
action: string;
|
||||
count?: number;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export const memoryTool: AgentTool<typeof memorySchema, MemoryToolDetails> = {
|
||||
name: "memory",
|
||||
label: "Memory",
|
||||
description: "Persistent memory across sessions. Save facts, recall by query, list all memories, or delete by ID. Stored in ~/.jae/memory/.",
|
||||
parameters: memorySchema,
|
||||
async execute(toolCallId, params, signal) {
|
||||
const { action, content, query, tags = [], id, limit = 5 } = params;
|
||||
const memories = loadMemories();
|
||||
|
||||
if (action === "save") {
|
||||
if (!content) return { content: [{ type: "text", text: "Error: content is required for save action" }], details: { action } };
|
||||
const entry: MemoryEntry = {
|
||||
id: `mem-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
||||
content,
|
||||
tags,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
memories.push(entry);
|
||||
saveMemories(memories);
|
||||
return { content: [{ type: "text", text: `Memory saved. ID: ${entry.id}` }], details: { action, id: entry.id } };
|
||||
}
|
||||
|
||||
if (action === "recall") {
|
||||
if (!query) return { content: [{ type: "text", text: "Error: query is required for recall action" }], details: { action } };
|
||||
const scored = memories
|
||||
.map(m => ({ m, score: scoreMatch(m, query) }))
|
||||
.filter(x => x.score > 0)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, limit)
|
||||
.map(x => x.m);
|
||||
const text = scored.length === 0
|
||||
? `No memories found for: ${query}`
|
||||
: scored.map(m => `[${m.id}] ${m.content}${m.tags.length ? ` (tags: ${m.tags.join(", ")})` : ""}`).join("\n\n");
|
||||
return { content: [{ type: "text", text }], details: { action, count: scored.length } };
|
||||
}
|
||||
|
||||
if (action === "list") {
|
||||
const text = memories.length === 0
|
||||
? "No memories stored."
|
||||
: memories.map(m => `[${m.id}] ${m.content.slice(0, 100)}${m.content.length > 100 ? "..." : ""}`).join("\n");
|
||||
return { content: [{ type: "text", text }], details: { action, count: memories.length } };
|
||||
}
|
||||
|
||||
if (action === "delete") {
|
||||
if (!id) return { content: [{ type: "text", text: "Error: id is required for delete action" }], details: { action } };
|
||||
const filtered = memories.filter(m => m.id !== id);
|
||||
if (filtered.length === memories.length) return { content: [{ type: "text", text: `No memory found with ID: ${id}` }], details: { action } };
|
||||
saveMemories(filtered);
|
||||
return { content: [{ type: "text", text: `Memory ${id} deleted.` }], details: { action, id } };
|
||||
}
|
||||
|
||||
return { content: [{ type: "text", text: `Unknown action: ${action}` }], details: { action } };
|
||||
},
|
||||
};
|
||||
67
packages/coding-agent/src/core/tools/web-fetch.ts
Normal file
67
packages/coding-agent/src/core/tools/web-fetch.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
|
||||
import type { AgentTool } from "@jaeswift/jae-agent-core";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
|
||||
const webFetchSchema = Type.Object({
|
||||
url: Type.String({ description: "URL to fetch" }),
|
||||
selector: Type.Optional(Type.String({ description: "CSS selector to extract specific content (optional)" })),
|
||||
});
|
||||
|
||||
export type WebFetchToolInput = Static<typeof webFetchSchema>;
|
||||
|
||||
export interface WebFetchToolDetails {
|
||||
url: string;
|
||||
status: number;
|
||||
contentType: string;
|
||||
truncated: boolean;
|
||||
}
|
||||
|
||||
const MAX_CHARS = 20000;
|
||||
|
||||
function htmlToText(html: string): string {
|
||||
return html
|
||||
.replace(/<script[\s\S]*?<\/script>/gi, "")
|
||||
.replace(/<style[\s\S]*?<\/style>/gi, "")
|
||||
.replace(/<[^>]+>/g, " ")
|
||||
.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
||||
.replace(/"/g, '"').replace(/ /g, " ").replace(/'/g, "'")
|
||||
.replace(/[ \t]+/g, " ")
|
||||
.replace(/\n{3,}/g, "\n\n")
|
||||
.trim();
|
||||
}
|
||||
|
||||
export const webFetchTool: AgentTool<typeof webFetchSchema, WebFetchToolDetails> = {
|
||||
name: "web_fetch",
|
||||
label: "Web Fetch",
|
||||
description: "Fetch and read the text content of any web page or URL. Strips HTML to plain text.",
|
||||
parameters: webFetchSchema,
|
||||
async execute(toolCallId, params, signal) {
|
||||
const { url } = params;
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
headers: { "User-Agent": "JAE-Agent/1.0", "Accept": "text/html,application/xhtml+xml,text/plain,*/*" },
|
||||
signal: signal ?? AbortSignal.timeout(15000),
|
||||
redirect: "follow",
|
||||
});
|
||||
const contentType = res.headers.get("content-type") || "";
|
||||
let body = await res.text();
|
||||
let text: string;
|
||||
if (contentType.includes("html")) {
|
||||
text = htmlToText(body);
|
||||
} else {
|
||||
text = body;
|
||||
}
|
||||
const truncated = text.length > MAX_CHARS;
|
||||
if (truncated) text = text.slice(0, MAX_CHARS) + "\n... [truncated]";
|
||||
return {
|
||||
content: [{ type: "text", text: `URL: ${url}\nStatus: ${res.status}\n\n${text}` }],
|
||||
details: { url, status: res.status, contentType, truncated },
|
||||
};
|
||||
} catch (err: any) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Fetch failed for ${url}: ${err.message}` }],
|
||||
details: { url, status: 0, contentType: "", truncated: false },
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
99
packages/coding-agent/src/core/tools/web-search.ts
Normal file
99
packages/coding-agent/src/core/tools/web-search.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
|
||||
import type { AgentTool } from "@jaeswift/jae-agent-core";
|
||||
import { Text } from "@jaeswift/jae-tui";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { theme } from "../../modes/interactive/theme/theme.js";
|
||||
import type { ToolRenderResultOptions } from "../extensions/types.js";
|
||||
import { getTextOutput } from "./render-utils.js";
|
||||
|
||||
const webSearchSchema = Type.Object({
|
||||
query: Type.String({ description: "Search query" }),
|
||||
limit: Type.Optional(Type.Number({ description: "Max results to return (default: 5)" })),
|
||||
});
|
||||
|
||||
export type WebSearchToolInput = Static<typeof webSearchSchema>;
|
||||
|
||||
export interface WebSearchResult {
|
||||
title: string;
|
||||
url: string;
|
||||
snippet: string;
|
||||
}
|
||||
|
||||
export interface WebSearchToolDetails {
|
||||
results: WebSearchResult[];
|
||||
query: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
async function fetchDuckDuckGo(query: string, limit: number): Promise<WebSearchResult[]> {
|
||||
const encoded = encodeURIComponent(query);
|
||||
const url = `https://api.duckduckgo.com/?q=${encoded}&format=json&no_redirect=1&no_html=1&skip_disambig=1`;
|
||||
const res = await fetch(url, {
|
||||
headers: { "User-Agent": "JAE-Agent/1.0" },
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
if (!res.ok) throw new Error(`DuckDuckGo returned ${res.status}`);
|
||||
const data = await res.json() as any;
|
||||
|
||||
const results: WebSearchResult[] = [];
|
||||
|
||||
// Instant answer
|
||||
if (data.AbstractText && data.AbstractURL) {
|
||||
results.push({ title: data.Heading || query, url: data.AbstractURL, snippet: data.AbstractText });
|
||||
}
|
||||
|
||||
// Related topics
|
||||
for (const topic of (data.RelatedTopics || [])) {
|
||||
if (results.length >= limit) break;
|
||||
if (topic.FirstURL && topic.Text) {
|
||||
results.push({ title: topic.Text.split(" - ")[0] || topic.Text, url: topic.FirstURL, snippet: topic.Text });
|
||||
} else if (topic.Topics) {
|
||||
for (const sub of topic.Topics) {
|
||||
if (results.length >= limit) break;
|
||||
if (sub.FirstURL && sub.Text) {
|
||||
results.push({ title: sub.Text.split(" - ")[0] || sub.Text, url: sub.FirstURL, snippet: sub.Text });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Results section
|
||||
for (const r of (data.Results || [])) {
|
||||
if (results.length >= limit) break;
|
||||
if (r.FirstURL && r.Text) {
|
||||
results.push({ title: r.Title || r.Text, url: r.FirstURL, snippet: r.Text });
|
||||
}
|
||||
}
|
||||
|
||||
return results.slice(0, limit);
|
||||
}
|
||||
|
||||
export const webSearchTool: AgentTool<typeof webSearchSchema, WebSearchToolDetails> = {
|
||||
name: "web_search",
|
||||
label: "Web Search",
|
||||
description: "Search the web for current information using DuckDuckGo. Returns titles, URLs, and snippets.",
|
||||
parameters: webSearchSchema,
|
||||
async execute(toolCallId, params, signal) {
|
||||
const { query, limit = 5 } = params;
|
||||
try {
|
||||
const results = await fetchDuckDuckGo(query, limit);
|
||||
const text = results.length === 0
|
||||
? `No results found for: ${query}`
|
||||
: results.map((r, i) => `[${i + 1}] ${r.title}
|
||||
URL: ${r.url}
|
||||
${r.snippet}`).join("
|
||||
|
||||
");
|
||||
return {
|
||||
content: [{ type: "text", text }],
|
||||
details: { results, query },
|
||||
};
|
||||
} catch (err: any) {
|
||||
const msg = `Web search failed: ${err.message}`;
|
||||
return {
|
||||
content: [{ type: "text", text: msg }],
|
||||
details: { results: [], query, error: err.message },
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { Alert } from "@mariozechner/mini-lit/dist/Alert.js";
|
||||
import type { Message } from "@jaeswift/jae-ai";
|
||||
import type { AgentMessage, MessageRenderer } from "@jaeswift/jae-web-ui";
|
||||
import { defaultConvertToLlm, registerMessageRenderer } from "@jaeswift/jae-web-ui";
|
||||
import { Alert } from "@mariozechner/mini-lit/dist/Alert.js";
|
||||
import { html } from "lit";
|
||||
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ Feel free to use these tools when needed to provide accurate and helpful respons
|
|||
// Create javascript_repl tool with access to attachments + artifacts
|
||||
const replTool = createJavaScriptReplTool();
|
||||
replTool.runtimeProvidersFactory = runtimeProvidersFactory;
|
||||
return [replTool];
|
||||
return [replTool, createWebSearchTool(), createImageGenTool(), createTTSTool()];
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { Model } from "@jaeswift/jae-ai";
|
||||
import { icon } from "@mariozechner/mini-lit";
|
||||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
import { Select, type SelectOption } from "@mariozechner/mini-lit/dist/Select.js";
|
||||
import type { Model } from "@jaeswift/jae-ai";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { createRef, ref } from "lit/directives/ref.js";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { type Context, complete, getModel } from "@jaeswift/jae-ai";
|
||||
import { i18n } from "@mariozechner/mini-lit";
|
||||
import { Badge } from "@mariozechner/mini-lit/dist/Badge.js";
|
||||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
import { type Context, complete, getModel } from "@jaeswift/jae-ai";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { getAppStorage } from "../storage/app-storage.js";
|
||||
|
|
|
|||
131
packages/web-ui/src/components/VeniceModelBrowser.ts
Normal file
131
packages/web-ui/src/components/VeniceModelBrowser.ts
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
|
||||
import { getModels } from "@jaeswift/jae-ai";
|
||||
import { html, LitElement, type TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { Badge } from "@mariozechner/mini-lit/dist/Badge.js";
|
||||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
|
||||
const TAG_COLORS: Record<string, string> = {
|
||||
tools: "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300",
|
||||
vision: "bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300",
|
||||
reasoning: "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300",
|
||||
code: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300",
|
||||
"image-generation": "bg-pink-100 text-pink-800 dark:bg-pink-900 dark:text-pink-300",
|
||||
"video-generation": "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300",
|
||||
tts: "bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-300",
|
||||
asr: "bg-teal-100 text-teal-800 dark:bg-teal-900 dark:text-teal-300",
|
||||
embedding: "bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300",
|
||||
"e2ee": "bg-emerald-100 text-emerald-800 dark:bg-emerald-900 dark:text-emerald-300",
|
||||
"web-search": "bg-cyan-100 text-cyan-800 dark:bg-cyan-900 dark:text-cyan-300",
|
||||
};
|
||||
|
||||
const CATEGORY_LABELS: Record<string, string> = {
|
||||
text: "💬 Text & Chat",
|
||||
image: "🖼️ Image Generation",
|
||||
video: "🎬 Video Generation",
|
||||
audio: "🔊 Audio (TTS / ASR)",
|
||||
other: "🔧 Other",
|
||||
};
|
||||
|
||||
function categoriseModel(tags: string[] = []): string {
|
||||
if (tags.includes("image-generation") || tags.includes("inpainting")) return "image";
|
||||
if (tags.includes("video-generation")) return "video";
|
||||
if (tags.includes("tts") || tags.includes("asr")) return "audio";
|
||||
if (tags.includes("embedding") || tags.includes("upscaling")) return "other";
|
||||
return "text";
|
||||
}
|
||||
|
||||
@customElement("venice-model-browser")
|
||||
export class VeniceModelBrowser extends LitElement {
|
||||
@state() private filter: string = "all";
|
||||
@state() private search: string = "";
|
||||
|
||||
protected createRenderRoot() { return this; }
|
||||
|
||||
private renderTag(tag: string): TemplateResult {
|
||||
const cls = TAG_COLORS[tag] ?? "bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300";
|
||||
return html`<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium ${cls}">${tag}</span>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
let models: any[] = [];
|
||||
try {
|
||||
models = (getModels("venice" as any) as any[]) || [];
|
||||
} catch { models = []; }
|
||||
|
||||
// Group by category
|
||||
const grouped: Record<string, any[]> = { text: [], image: [], video: [], audio: [], other: [] };
|
||||
for (const m of models) {
|
||||
const cat = categoriseModel(m.tags);
|
||||
if (!grouped[cat]) grouped[cat] = [];
|
||||
grouped[cat].push(m);
|
||||
}
|
||||
|
||||
const filters = [
|
||||
{ id: "all", label: `All (${models.length})` },
|
||||
...Object.entries(grouped)
|
||||
.filter(([, ms]) => ms.length > 0)
|
||||
.map(([cat, ms]) => ({ id: cat, label: `${CATEGORY_LABELS[cat] ?? cat} (${ms.length})` })),
|
||||
];
|
||||
|
||||
const searchLower = this.search.toLowerCase();
|
||||
const activeGroups = Object.entries(grouped).filter(([cat, ms]) => {
|
||||
if (this.filter !== "all" && cat !== this.filter) return false;
|
||||
return ms.length > 0;
|
||||
});
|
||||
|
||||
return html`
|
||||
<div class="flex flex-col gap-4">
|
||||
<!-- Filter bar -->
|
||||
<div class="flex flex-wrap gap-2 items-center">
|
||||
${filters.map(f => html`
|
||||
<button
|
||||
class="px-3 py-1 rounded-full text-xs font-medium border transition-colors
|
||||
${this.filter === f.id
|
||||
? "bg-primary text-primary-foreground border-primary"
|
||||
: "border-border text-muted-foreground hover:bg-secondary"}"
|
||||
@click=${() => { this.filter = f.id; this.requestUpdate(); }}
|
||||
>${f.label}</button>
|
||||
`)}
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Search models..."
|
||||
class="ml-auto px-3 py-1 text-sm rounded border border-border bg-background text-foreground w-48"
|
||||
.value=${this.search}
|
||||
@input=${(e: Event) => { this.search = (e.target as HTMLInputElement).value; this.requestUpdate(); }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Model groups -->
|
||||
${activeGroups.map(([cat, ms]) => {
|
||||
const filtered = searchLower ? ms.filter((m: any) => m.id.toLowerCase().includes(searchLower)) : ms;
|
||||
if (!filtered.length) return html``;
|
||||
return html`
|
||||
<div class="flex flex-col gap-2">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase tracking-wider">${CATEGORY_LABELS[cat] ?? cat}</h4>
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
${filtered.map((m: any) => html`
|
||||
<div class="flex items-start justify-between p-3 rounded-lg border border-border bg-card hover:bg-secondary/40 transition-colors">
|
||||
<div class="flex flex-col gap-1 min-w-0">
|
||||
<span class="text-sm font-mono font-medium text-foreground truncate">${m.id}</span>
|
||||
${m.contextWindow ? html`<span class="text-xs text-muted-foreground">${(m.contextWindow / 1000).toFixed(0)}k ctx</span>` : ""}
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1 justify-end ml-2 shrink-0 max-w-48">
|
||||
${(m.tags ?? []).map((t: string) => this.renderTag(t))}
|
||||
</div>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
|
||||
${models.length === 0 ? html`
|
||||
<div class="text-sm text-muted-foreground text-center py-4">
|
||||
No Venice models found. Ensure jae-ai includes Venice models.
|
||||
</div>
|
||||
` : ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import type { Model } from "@jaeswift/jae-ai";
|
||||
import { i18n } from "@mariozechner/mini-lit";
|
||||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
import { DialogBase } from "@mariozechner/mini-lit/dist/DialogBase.js";
|
||||
import { Input } from "@mariozechner/mini-lit/dist/Input.js";
|
||||
import { Label } from "@mariozechner/mini-lit/dist/Label.js";
|
||||
import { Select } from "@mariozechner/mini-lit/dist/Select.js";
|
||||
import type { Model } from "@jaeswift/jae-ai";
|
||||
import { html, type TemplateResult } from "lit";
|
||||
import { state } from "lit/decorators.js";
|
||||
import { getAppStorage } from "../storage/app-storage.js";
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { getModels, getProviders, type Model, modelsAreEqual } from "@jaeswift/jae-ai";
|
||||
import { icon } from "@mariozechner/mini-lit";
|
||||
import { Badge } from "@mariozechner/mini-lit/dist/Badge.js";
|
||||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
import { DialogHeader } from "@mariozechner/mini-lit/dist/Dialog.js";
|
||||
import { DialogBase } from "@mariozechner/mini-lit/dist/DialogBase.js";
|
||||
import { getModels, getProviders, type Model, modelsAreEqual } from "@jaeswift/jae-ai";
|
||||
import { html, type PropertyValues, type TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { createRef, ref } from "lit/directives/ref.js";
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { getProviders } from "@jaeswift/jae-ai";
|
||||
import { i18n } from "@mariozechner/mini-lit";
|
||||
import { Select } from "@mariozechner/mini-lit/dist/Select.js";
|
||||
import { getProviders } from "@jaeswift/jae-ai";
|
||||
import { html, type TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import "../components/CustomProviderCard.js";
|
||||
import "../components/VeniceModelBrowser.js";
|
||||
import "../components/ProviderKeyInput.js";
|
||||
import { getAppStorage } from "../storage/app-storage.js";
|
||||
import type {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { getProviders } from "@jaeswift/jae-ai";
|
||||
import { i18n } from "@mariozechner/mini-lit";
|
||||
import { Dialog, DialogContent, DialogHeader } from "@mariozechner/mini-lit/dist/Dialog.js";
|
||||
import { Input } from "@mariozechner/mini-lit/dist/Input.js";
|
||||
import { Label } from "@mariozechner/mini-lit/dist/Label.js";
|
||||
import { Switch } from "@mariozechner/mini-lit/dist/Switch.js";
|
||||
import { getProviders } from "@jaeswift/jae-ai";
|
||||
import { html, LitElement, type TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import "../components/ProviderKeyInput.js";
|
||||
|
|
|
|||
|
|
@ -118,3 +118,5 @@ export { clearAuthToken, getAuthToken } from "./utils/auth-token.js";
|
|||
export { formatCost, formatModelCost, formatTokenCount, formatUsage } from "./utils/format.js";
|
||||
export { i18n, setLanguage, translations } from "./utils/i18n.js";
|
||||
export { applyProxyIfNeeded, createStreamFn, isCorsError, shouldUseProxyForProvider } from "./utils/proxy-utils.js";
|
||||
|
||||
export { VeniceModelBrowser } from "./components/VeniceModelBrowser.js";
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { icon } from "@mariozechner/mini-lit";
|
||||
import "@mariozechner/mini-lit/dist/MarkdownBlock.js";
|
||||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
import type { Agent, AgentMessage, AgentTool } from "@jaeswift/jae-agent-core";
|
||||
import { StringEnum, type ToolCall } from "@jaeswift/jae-ai";
|
||||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { html, LitElement, type TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
|
|
|
|||
|
|
@ -44,3 +44,7 @@ export function renderTool(
|
|||
}
|
||||
|
||||
export { getToolRenderer, registerToolRenderer };
|
||||
|
||||
export { webSearchTool, createWebSearchTool, type WebSearchDetails, type WebSearchResult } from "./web-search.js";
|
||||
export { imageGenTool, createImageGenTool, type ImageGenDetails } from "./image-gen.js";
|
||||
export { ttsTool, createTTSTool, type TTSDetails } from "./voice-tts.js";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { i18n } from "@mariozechner/mini-lit";
|
||||
import type { AgentTool } from "@jaeswift/jae-agent-core";
|
||||
import type { ToolResultMessage } from "@jaeswift/jae-ai";
|
||||
import { i18n } from "@mariozechner/mini-lit";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { html } from "lit";
|
||||
import { createRef, ref } from "lit/directives/ref.js";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { i18n } from "@mariozechner/mini-lit";
|
||||
import type { Usage } from "@jaeswift/jae-ai";
|
||||
import { i18n } from "@mariozechner/mini-lit";
|
||||
|
||||
export function formatCost(cost: number): string {
|
||||
return `$${cost.toFixed(4)}`;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { LMStudioClient } from "@lmstudio/sdk";
|
||||
import type { Model } from "@jaeswift/jae-ai";
|
||||
import { LMStudioClient } from "@lmstudio/sdk";
|
||||
import { Ollama } from "ollama/browser";
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue