agent tool and toolbox
- created AiTool and AiToolbox for representing tools in the API - add googleapis dependency - integrate Google Search tool as first agent tool - created IAiEnvironment to communicate AI environment vars around the platform
This commit is contained in:
parent
819654e20a
commit
f8dbb2e08a
@ -94,6 +94,12 @@ export default {
|
||||
sameSite: yamlConfig.session?.cookie?.sameSite || false,
|
||||
},
|
||||
},
|
||||
google: {
|
||||
cse: {
|
||||
apiKey: yamlConfig.google.cse.apiKey,
|
||||
engineId: yamlConfig.google.cse.engineId,
|
||||
},
|
||||
},
|
||||
mongodb: {
|
||||
host: yamlConfig.mongodb?.host || "localhost",
|
||||
database: yamlConfig.mongodb?.database || "",
|
||||
|
||||
@ -2,6 +2,16 @@
|
||||
// Copyright (C) 2026 Robert Colbert <rob.colbert@openplatform.us>
|
||||
// All Rights Reserved
|
||||
|
||||
import env from "./config/env.js";
|
||||
const aiEnv: IAiEnvironment = {
|
||||
NODE_ENV: env.NODE_ENV || "develop",
|
||||
google: {
|
||||
cse: {
|
||||
apiKey: env.google.cse.apiKey,
|
||||
engineId: env.google.cse.engineId,
|
||||
},
|
||||
},
|
||||
};
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import "./lib/db.js";
|
||||
@ -30,7 +40,7 @@ import {
|
||||
* App Logic
|
||||
*/
|
||||
|
||||
import { createAiApi, type IAiLogger } from "@gadget/ai";
|
||||
import { createAiApi, IAiEnvironment, type IAiLogger } from "@gadget/ai";
|
||||
|
||||
import {
|
||||
IUser,
|
||||
@ -551,6 +561,7 @@ class DtpWebCli extends DtpProcess {
|
||||
};
|
||||
|
||||
const api = createAiApi(
|
||||
aiEnv,
|
||||
{
|
||||
_id: provider._id,
|
||||
name: provider.name,
|
||||
|
||||
@ -37,7 +37,7 @@ async function readJsonFile<T>(filePath: string): Promise<T> {
|
||||
|
||||
/* eslint-disable no-process-env */
|
||||
export default {
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
NODE_ENV: process.env.NODE_ENV || "develop",
|
||||
timezone: yamlConfig.timezone || "America/New_York",
|
||||
installDir: INSTALL_DIR,
|
||||
pkg: await readJsonFile<typeof PackageJson>(
|
||||
@ -47,6 +47,12 @@ export default {
|
||||
baseUrl: yamlConfig.platform.baseUrl,
|
||||
gadgetKey: yamlConfig.platform.gadgetKey,
|
||||
},
|
||||
google: {
|
||||
cse: {
|
||||
apiKey: yamlConfig.google?.cse?.apiKey,
|
||||
engineId: yamlConfig.google?.cse?.engineId,
|
||||
},
|
||||
},
|
||||
log: {
|
||||
console: {
|
||||
enabled: yamlConfig.logging?.console?.enabled === true,
|
||||
|
||||
@ -2,8 +2,10 @@
|
||||
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
import { IAiProvider as DbAiProvider } from "@gadget/api";
|
||||
import { GadgetService } from "../lib/service.ts";
|
||||
import env from "../config/env.ts";
|
||||
|
||||
import { IAiProvider as DbAiProvider, GadgetId } from "@gadget/api";
|
||||
import { GadgetService } from "../lib/service.js";
|
||||
import {
|
||||
type IAiChatOptions,
|
||||
type IAiChatResponse,
|
||||
@ -13,8 +15,8 @@ import {
|
||||
type IAiProvider as AiProviderConfig,
|
||||
type IAiResponseStreamFn,
|
||||
createAiApi,
|
||||
IAiEnvironment,
|
||||
} from "@gadget/ai";
|
||||
import { GadgetId } from "../../../packages/api/dist/lib/gadget-id.js";
|
||||
|
||||
/**
|
||||
* Drone-specific model config that accepts the database provider type.
|
||||
@ -126,7 +128,16 @@ class AiService extends GadgetService {
|
||||
}
|
||||
|
||||
getApi(provider: AiProviderConfig) {
|
||||
return createAiApi(provider, this.log);
|
||||
const aiEnv: IAiEnvironment = {
|
||||
NODE_ENV: env.NODE_ENV,
|
||||
google: {
|
||||
cse: {
|
||||
apiKey: env.google.cse.apiKey,
|
||||
engineId: env.google.cse.engineId,
|
||||
},
|
||||
},
|
||||
};
|
||||
return createAiApi(aiEnv, provider, this.log);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -15,10 +15,17 @@
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch"
|
||||
},
|
||||
"keywords": ["gadget", "ai", "ollama", "openai", "abstraction"],
|
||||
"keywords": [
|
||||
"gadget",
|
||||
"ai",
|
||||
"agent",
|
||||
"ollama",
|
||||
"openai"
|
||||
],
|
||||
"author": "Rob Colbert",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"googleapis": "^171.4.0",
|
||||
"numeral": "^2.0.6",
|
||||
"ollama": "^0.6.3",
|
||||
"openai": "^6.34.0"
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
import { IAiEnvironment } from "./config/env.ts";
|
||||
import { AiTool } from "./tools/tool.ts";
|
||||
|
||||
export type AiSdkType = "ollama" | "openai";
|
||||
|
||||
export interface IAiProvider {
|
||||
@ -63,6 +66,7 @@ export interface IAiChatOptions {
|
||||
systemPrompt?: string;
|
||||
userPrompt?: string;
|
||||
context?: IContextChatMessage[];
|
||||
tools?: AiTool[];
|
||||
}
|
||||
|
||||
export interface IToolCall {
|
||||
@ -133,10 +137,12 @@ export interface IAiModelProbeResult {
|
||||
}
|
||||
|
||||
export abstract class AiApi {
|
||||
protected env: IAiEnvironment;
|
||||
protected provider: IAiProvider;
|
||||
protected log: IAiLogger;
|
||||
|
||||
constructor(provider: IAiProvider, logger?: IAiLogger) {
|
||||
constructor(env: IAiEnvironment, provider: IAiProvider, logger?: IAiLogger) {
|
||||
this.env = env;
|
||||
this.provider = provider;
|
||||
this.log = logger ?? defaultLogger();
|
||||
}
|
||||
|
||||
12
packages/ai/src/config/env.ts
Normal file
12
packages/ai/src/config/env.ts
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
export interface IAiEnvironment {
|
||||
NODE_ENV: string;
|
||||
google: {
|
||||
cse: {
|
||||
apiKey: string | undefined;
|
||||
engineId: string | undefined;
|
||||
};
|
||||
};
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
export { IAiEnvironment } from "./config/env.ts";
|
||||
|
||||
export {
|
||||
type AiSdkType,
|
||||
type IAiProvider,
|
||||
@ -29,13 +31,18 @@ import { OpenAiApi } from "./openai.js";
|
||||
import type { IAiProvider } from "./api.js";
|
||||
import type { IAiLogger } from "./api.js";
|
||||
import type { AiApi } from "./api.js";
|
||||
import { IAiEnvironment } from "./config/env.ts";
|
||||
|
||||
export function createAiApi(provider: IAiProvider, logger?: IAiLogger): AiApi {
|
||||
export function createAiApi(
|
||||
env: IAiEnvironment,
|
||||
provider: IAiProvider,
|
||||
logger?: IAiLogger,
|
||||
): AiApi {
|
||||
switch (provider.sdk) {
|
||||
case "ollama":
|
||||
return new OllamaAiApi(provider, logger);
|
||||
return new OllamaAiApi(env, provider, logger);
|
||||
case "openai":
|
||||
return new OpenAiApi(provider, logger);
|
||||
return new OpenAiApi(env, provider, logger);
|
||||
default:
|
||||
throw new Error(`Unknown AI SDK: ${(provider as IAiProvider).sdk}`);
|
||||
}
|
||||
|
||||
@ -19,12 +19,13 @@ import {
|
||||
IAiProvider,
|
||||
IAiResponseStreamFn,
|
||||
} from "./api.js";
|
||||
import { IAiEnvironment } from "./config/env.ts";
|
||||
|
||||
export class OllamaAiApi extends AiApi {
|
||||
protected client: Ollama;
|
||||
|
||||
constructor(provider: IAiProvider, logger?: IAiLogger) {
|
||||
super(provider, logger);
|
||||
constructor(env: IAiEnvironment, provider: IAiProvider, logger?: IAiLogger) {
|
||||
super(env, provider, logger);
|
||||
this.client = new Ollama({
|
||||
host: this.provider.baseUrl,
|
||||
headers: { Authorization: `Bearer ${this.provider.apiKey}` },
|
||||
@ -105,7 +106,8 @@ export class OllamaAiApi extends AiApi {
|
||||
!!modelInfo?.["clip"],
|
||||
hasEmbedding: capabilities.includes("embeddings"),
|
||||
hasThinking: capabilities.includes("reasoning"),
|
||||
isInstructTuned: modelId.toLowerCase().includes("instruct") ||
|
||||
isInstructTuned:
|
||||
modelId.toLowerCase().includes("instruct") ||
|
||||
modelId.toLowerCase().includes("chat") ||
|
||||
modelId.toLowerCase().includes("-it"),
|
||||
};
|
||||
|
||||
@ -17,6 +17,11 @@ import {
|
||||
IAiProvider,
|
||||
IAiResponseStreamFn,
|
||||
} from "./api.js";
|
||||
import {
|
||||
ChatCompletionFunctionTool,
|
||||
ChatCompletionTool,
|
||||
} from "openai/resources";
|
||||
import { IAiEnvironment } from "./config/env.ts";
|
||||
|
||||
interface GabAiCapabilities {
|
||||
text?: boolean;
|
||||
@ -50,8 +55,8 @@ interface OpenAIModelInfo {
|
||||
export class OpenAiApi extends AiApi {
|
||||
protected client: OpenAI;
|
||||
|
||||
constructor(provider: IAiProvider, logger?: IAiLogger) {
|
||||
super(provider, logger);
|
||||
constructor(env: IAiEnvironment, provider: IAiProvider, logger?: IAiLogger) {
|
||||
super(env, provider, logger);
|
||||
this.client = new OpenAI({
|
||||
baseURL: provider.baseUrl,
|
||||
apiKey: provider.apiKey,
|
||||
@ -127,12 +132,15 @@ export class OpenAiApi extends AiApi {
|
||||
return {
|
||||
capabilities: {
|
||||
canCallTools: modelId.toLowerCase().includes("gpt"),
|
||||
hasVision: modelId.toLowerCase().includes("vision") ||
|
||||
hasVision:
|
||||
modelId.toLowerCase().includes("vision") ||
|
||||
modelId.toLowerCase().includes("4o") ||
|
||||
modelId.toLowerCase().includes("image"),
|
||||
hasEmbedding: modelId.toLowerCase().includes("embedding") ||
|
||||
hasEmbedding:
|
||||
modelId.toLowerCase().includes("embedding") ||
|
||||
modelId.toLowerCase().includes("embed"),
|
||||
hasThinking: modelId.toLowerCase().includes("o1") ||
|
||||
hasThinking:
|
||||
modelId.toLowerCase().includes("o1") ||
|
||||
modelId.toLowerCase().includes("o3") ||
|
||||
modelId.toLowerCase().includes("reasoning"),
|
||||
isInstructTuned: true,
|
||||
@ -242,9 +250,24 @@ export class OpenAiApi extends AiApi {
|
||||
messages.push({ role: "user" as const, content: options.userPrompt });
|
||||
}
|
||||
|
||||
const tools: ChatCompletionTool[] = options.tools
|
||||
? options.tools.map((tool) => {
|
||||
const openaiTool: ChatCompletionFunctionTool = {
|
||||
type: tool.definition.type,
|
||||
function: {
|
||||
name: tool.definition.function.name,
|
||||
description: tool.definition.function.description,
|
||||
parameters: tool.definition.function.parameters,
|
||||
},
|
||||
};
|
||||
return openaiTool;
|
||||
})
|
||||
: [];
|
||||
|
||||
const response = await this.client.chat.completions.create({
|
||||
model: model.modelId,
|
||||
messages,
|
||||
tools,
|
||||
stream: false,
|
||||
});
|
||||
|
||||
|
||||
76
packages/ai/src/toolbox.ts
Normal file
76
packages/ai/src/toolbox.ts
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
import { IAiEnvironment } from "./config/env.ts";
|
||||
import { AiTool } from "./tools/tool.ts";
|
||||
|
||||
export type ToolMap = Map<string, AiTool>;
|
||||
export type ToolSet = Set<AiTool>;
|
||||
|
||||
/**
|
||||
* No. I don't want to create an "MCP" server. I just want an in-process
|
||||
* toolbox that the agents can use in their daily work. That's not too much to
|
||||
* ask, dammit.
|
||||
*/
|
||||
export class AiToolbox {
|
||||
private _env: IAiEnvironment;
|
||||
get env(): IAiEnvironment {
|
||||
return this._env;
|
||||
}
|
||||
|
||||
private tools: ToolMap = new Map<string, AiTool>();
|
||||
private modeSets: Map<string, ToolSet> = new Map<string, Set<AiTool>>();
|
||||
|
||||
constructor(env: IAiEnvironment) {
|
||||
this._env = env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an AiTool instance for use by the platform. If no ChatSessionMode
|
||||
* modes are specified, you are registering a system tool - such as the chat
|
||||
* session auto-naming tool - which are not called by agents (they are called)
|
||||
* by the platform itself, deterministically.
|
||||
* @param tool the tool being registered for use by the platform
|
||||
* @param modes the optional name(s) of the mode for which the tool is being
|
||||
* registered
|
||||
*/
|
||||
register(tool: AiTool, modes?: string[]): void {
|
||||
if (this.tools.has(tool.name)) {
|
||||
throw new Error(`tool already registered: ${tool.name}`);
|
||||
}
|
||||
this.tools.set(tool.name, tool);
|
||||
|
||||
if (!modes) {
|
||||
return; // system tools aren't listed in the modes for agent use
|
||||
}
|
||||
|
||||
for (const mode of modes) {
|
||||
let set = this.modeSets.get(mode);
|
||||
if (!set) {
|
||||
set = new Set<AiTool>();
|
||||
this.modeSets.set(mode, set);
|
||||
}
|
||||
set.add(tool);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a tool instance from the toolbox by name, ignoring mode(s). This
|
||||
* is how the system fetches system tools for use.
|
||||
* @param name the name of the tool to be retrieved
|
||||
* @returns the tool, or undefined if the tool is not registered
|
||||
*/
|
||||
getTool(name: string): AiTool | undefined {
|
||||
return this.tools.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the set of tools registered for use in a given ChatSessionMode.
|
||||
* @param mode the ChatSessionMode for which a set of tools is being requested
|
||||
* @returns the set of tools, or undefined if there is not set for the mode
|
||||
* @todo the mode parameter should be the ChatSessionMode enum
|
||||
*/
|
||||
getModeSet(mode: string): ToolSet | undefined {
|
||||
return this.modeSets.get(mode);
|
||||
}
|
||||
}
|
||||
273
packages/ai/src/tools/search/google.ts
Normal file
273
packages/ai/src/tools/search/google.ts
Normal file
@ -0,0 +1,273 @@
|
||||
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
import { IAiLogger } from "../../api.ts";
|
||||
import { formatError } from "../tool-error.ts";
|
||||
import { AiTool, IToolArguments, IToolDefinition } from "../tool.ts";
|
||||
|
||||
import { google } from "googleapis";
|
||||
// import SearchService, { SearchServiceError } from "../../services/search.js";
|
||||
|
||||
export interface ISearchResult {
|
||||
title: string;
|
||||
link: string;
|
||||
snippet: string;
|
||||
image?: string;
|
||||
position?: number;
|
||||
displayLink?: string;
|
||||
}
|
||||
|
||||
export interface ISearchOptions {
|
||||
num?: number;
|
||||
siteSearch?: string;
|
||||
dateRestrict?: string;
|
||||
fileType?: string;
|
||||
safe?: "active" | "off";
|
||||
sort?: "relevance" | "date";
|
||||
start?: number;
|
||||
}
|
||||
|
||||
export class GoogleSearchTool extends AiTool {
|
||||
get name(): string {
|
||||
return "search_google";
|
||||
}
|
||||
|
||||
get category(): string {
|
||||
return "search";
|
||||
}
|
||||
|
||||
public definition: IToolDefinition = {
|
||||
type: "function",
|
||||
function: {
|
||||
name: this.name,
|
||||
description:
|
||||
"Perform a Google search for relevant information on the web.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description: "The search query string.",
|
||||
},
|
||||
num_results: {
|
||||
type: "number",
|
||||
description:
|
||||
"Number of search results to return (default: 10, max: 10).",
|
||||
},
|
||||
siteSearch: {
|
||||
type: "string",
|
||||
description:
|
||||
"Optional site to restrict the search to (e.g. github.com).",
|
||||
},
|
||||
dateRestrict: {
|
||||
type: "string",
|
||||
description:
|
||||
"Restricts results to documents based on a date range. Examples: d1 (last day), d7 (last week), d30 (last month), d365 (last year).",
|
||||
},
|
||||
fileType: {
|
||||
type: "string",
|
||||
description:
|
||||
"Restricts results to files of a specified extension. Examples: pdf, doc, xls, ppt.",
|
||||
},
|
||||
sort: {
|
||||
type: "string",
|
||||
description:
|
||||
"Sort order for results. Values: 'relevance' (default) or 'date'.",
|
||||
enum: ["relevance", "date"],
|
||||
},
|
||||
start: {
|
||||
type: "number",
|
||||
description:
|
||||
"The index of the first result to return (for pagination). Default: 1.",
|
||||
},
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
public async execute(
|
||||
args: IToolArguments,
|
||||
logger: IAiLogger,
|
||||
): Promise<string> {
|
||||
const { query } = args;
|
||||
|
||||
if (!query || typeof query !== "string" || query.trim().length === 0) {
|
||||
return formatError({
|
||||
code: "MISSING_PARAMETER",
|
||||
message: "The 'query' parameter is required.",
|
||||
parameter: "query",
|
||||
expected: "A non-empty string containing the search query.",
|
||||
example: 'search_google(query: "latest AI news")',
|
||||
recoveryHint:
|
||||
"Provide a 'query' parameter with your search terms and try again.",
|
||||
});
|
||||
}
|
||||
|
||||
logger.debug("performing Google search for user", { args });
|
||||
|
||||
try {
|
||||
const {
|
||||
num_results = 10,
|
||||
siteSearch,
|
||||
dateRestrict,
|
||||
fileType,
|
||||
sort,
|
||||
start,
|
||||
} = args;
|
||||
|
||||
const results = await this.search(query, {
|
||||
num: Math.min(num_results as number, 10),
|
||||
siteSearch: siteSearch as string | undefined,
|
||||
dateRestrict: dateRestrict as string | undefined,
|
||||
fileType: fileType as string | undefined,
|
||||
safe: "active",
|
||||
sort: sort as "relevance" | "date" | undefined,
|
||||
start: start as number | undefined,
|
||||
});
|
||||
|
||||
logger.debug("Google search results", { results });
|
||||
|
||||
let content = "";
|
||||
if (results && results.length) {
|
||||
content += `Here are some relevant search results I found:\n\n`;
|
||||
for (const result of results) {
|
||||
const title = JSON.stringify(result.title || "").slice(1, -1);
|
||||
const link = JSON.stringify(result.link || "").slice(1, -1);
|
||||
const snippet = JSON.stringify(result.snippet || "").slice(1, -1);
|
||||
const displayLink = result.displayLink
|
||||
? JSON.stringify(result.displayLink).slice(1, -1)
|
||||
: "";
|
||||
|
||||
content += `Title: ${title}\n`;
|
||||
content += `Link: ${link}\n`;
|
||||
if (displayLink) {
|
||||
content += `Source: ${displayLink}\n`;
|
||||
}
|
||||
content += `Snippet: ${snippet}\n\n`;
|
||||
}
|
||||
} else {
|
||||
content += "No relevant search results found.";
|
||||
}
|
||||
|
||||
return content;
|
||||
} catch (error: any) {
|
||||
// Generic error handling
|
||||
return formatError({
|
||||
code: "OPERATION_FAILED",
|
||||
message: `Failed to perform search: ${error.message}`,
|
||||
recoveryHint: "Please try again or check your search query.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async search(
|
||||
query: string,
|
||||
options: ISearchOptions,
|
||||
): Promise<ISearchResult[]> {
|
||||
const customSearch = google.customsearch({
|
||||
version: "v1",
|
||||
auth: this.toolbox.env.google.cse.apiKey,
|
||||
});
|
||||
|
||||
const params: any = {
|
||||
q: query,
|
||||
cx: this.toolbox.env.google.cse.engineId,
|
||||
num: options.num || 10,
|
||||
};
|
||||
|
||||
if (options.siteSearch) {
|
||||
params.siteSearch = options.siteSearch;
|
||||
}
|
||||
if (options.dateRestrict) {
|
||||
params.dateRestrict = options.dateRestrict;
|
||||
}
|
||||
if (options.fileType) {
|
||||
params.fileType = options.fileType;
|
||||
}
|
||||
if (options.safe) {
|
||||
params.safe = options.safe;
|
||||
}
|
||||
if (options.sort) {
|
||||
params.sort = options.sort;
|
||||
}
|
||||
if (options.start) {
|
||||
params.start = options.start;
|
||||
}
|
||||
|
||||
const response = await customSearch.cse.list(params);
|
||||
const results: ISearchResult[] = [];
|
||||
|
||||
if (response.data.items) {
|
||||
response.data.items.forEach((item: any, index: number) => {
|
||||
const result: ISearchResult = {
|
||||
title: item.title || "",
|
||||
link: item.link || "",
|
||||
snippet: item.snippet || "",
|
||||
position: item.position || index + 1,
|
||||
displayLink: item.displayLink || "",
|
||||
};
|
||||
|
||||
// Extract thumbnail if available
|
||||
if (item.pagemap?.cse_thumbnail?.[0]?.src) {
|
||||
result.image = item.pagemap.cse_thumbnail[0].src;
|
||||
}
|
||||
|
||||
results.push(result);
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Google CSE API errors and provide meaningful messages
|
||||
*/
|
||||
private parseCseError(error: any): string {
|
||||
// Handle Google API error structure
|
||||
if (error.response?.data?.error) {
|
||||
const apiError = error.response.data.error;
|
||||
const statusCode = error.response.status;
|
||||
|
||||
switch (statusCode) {
|
||||
case 401:
|
||||
formatError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: `401 Unauthorized - Invalid API key. ${apiError.message || ""}`,
|
||||
});
|
||||
case 403:
|
||||
return formatError({
|
||||
code: "FORBIDDEN",
|
||||
message: `403 Forbidden - ${apiError.message || "Access denied"}. Check your Engine ID and API key permissions.`,
|
||||
});
|
||||
case 429:
|
||||
const retryAfter = error.response.headers?.["retry-after"];
|
||||
return formatError({
|
||||
code: "RATE_LIMIT_EXCEEDED",
|
||||
message: `429 Too Many Requests - Rate limit exceeded. ${retryAfter ? `Retry after: ${retryAfter} seconds.` : ""} ${apiError.message || ""}`,
|
||||
});
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return formatError({
|
||||
code: "TOOL_EXECUTION_FAILED",
|
||||
message: `HTTP ${statusCode} - ${apiError.message || "Unknown error"}`,
|
||||
});
|
||||
}
|
||||
|
||||
// Handle network errors
|
||||
if (error.code === "ENOTFOUND") {
|
||||
return formatError({
|
||||
code: "NETWORK_ERROR",
|
||||
message: `Network error (code: ${error.code}) - Unable to reach Google API`,
|
||||
});
|
||||
}
|
||||
|
||||
// Generic error
|
||||
return formatError({
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
message: error.message || "An unknown error occurred",
|
||||
});
|
||||
}
|
||||
}
|
||||
50
packages/ai/src/tools/tool-error.ts
Normal file
50
packages/ai/src/tools/tool-error.ts
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
export type ToolErrorCode =
|
||||
| "MISSING_PARAMETER"
|
||||
| "INVALID_PARAMETER"
|
||||
| "NOT_FOUND"
|
||||
| "PERMISSION_DENIED"
|
||||
| "OPERATION_FAILED"
|
||||
| "OPERATION_NOT_ALLOWED"
|
||||
| "VALIDATION_ERROR"
|
||||
| "RATE_LIMITED"
|
||||
| "LIMIT_EXCEEDED"
|
||||
| "INVALID_CRON_SPEC"
|
||||
| "INVALID_OPERATION"
|
||||
| "TIMEOUT"
|
||||
| "INVALID_TOOL_ARGUMENTS"
|
||||
| "SUBAGENT_FAILED"
|
||||
| "TOOL_EXECUTION_FAILED"
|
||||
| "UNAUTHORIZED"
|
||||
| "FORBIDDEN"
|
||||
| "RATE_LIMIT_EXCEEDED"
|
||||
| "NETWORK_ERROR"
|
||||
| "SECURITY_VIOLATION";
|
||||
|
||||
export interface IToolError {
|
||||
code: ToolErrorCode;
|
||||
message: string;
|
||||
parameter?: string;
|
||||
expected?: string;
|
||||
example?: string;
|
||||
recoveryHint?: string;
|
||||
}
|
||||
|
||||
export function formatError(error: IToolError): string {
|
||||
const components: string[] = [`TOOL ERROR: ${error.code}`, error.message];
|
||||
if (error.parameter) {
|
||||
components.push(`PARAMETER: ${error.parameter}`);
|
||||
}
|
||||
if (error.expected) {
|
||||
components.push(`EXPECTED: ${error.expected}`);
|
||||
}
|
||||
if (error.example) {
|
||||
components.push(`EXAMPLE: ${error.example}`);
|
||||
}
|
||||
if (error.recoveryHint) {
|
||||
components.push(`RECOVERY HINT: ${error.recoveryHint}`);
|
||||
}
|
||||
return components.join("\n");
|
||||
}
|
||||
35
packages/ai/src/tools/tool.ts
Normal file
35
packages/ai/src/tools/tool.ts
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
|
||||
import { IAiLogger } from "../api.ts";
|
||||
import { AiToolbox } from "../toolbox.ts";
|
||||
|
||||
export interface IToolArguments {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface IToolDefinition {
|
||||
type: "function";
|
||||
function: {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: IToolArguments;
|
||||
};
|
||||
}
|
||||
|
||||
export abstract class AiTool {
|
||||
protected _toolbox: AiToolbox;
|
||||
get toolbox(): AiToolbox {
|
||||
return this._toolbox;
|
||||
}
|
||||
|
||||
constructor(toolbox: AiToolbox) {
|
||||
this._toolbox = toolbox;
|
||||
}
|
||||
|
||||
abstract get name(): string;
|
||||
abstract get category(): string;
|
||||
abstract get definition(): IToolDefinition;
|
||||
|
||||
abstract execute(args: IToolArguments, logger: IAiLogger): Promise<string>;
|
||||
}
|
||||
@ -30,6 +30,12 @@ export interface GadgetCodeConfig {
|
||||
sameSite?: boolean | "lax" | "strict" | "none";
|
||||
};
|
||||
};
|
||||
google: {
|
||||
cse: {
|
||||
apiKey: string;
|
||||
engineId: string;
|
||||
};
|
||||
};
|
||||
mongodb: {
|
||||
host: string;
|
||||
database: string;
|
||||
@ -120,6 +126,12 @@ export interface GadgetDroneConfig {
|
||||
baseUrl: string;
|
||||
gadgetKey: string;
|
||||
};
|
||||
google?: {
|
||||
cse?: {
|
||||
apiKey: string;
|
||||
engineId: string;
|
||||
};
|
||||
};
|
||||
logging?: {
|
||||
console?: {
|
||||
enabled?: boolean;
|
||||
|
||||
158
pnpm-lock.yaml
158
pnpm-lock.yaml
@ -315,6 +315,9 @@ importers:
|
||||
|
||||
packages/ai:
|
||||
dependencies:
|
||||
googleapis:
|
||||
specifier: ^171.4.0
|
||||
version: 171.4.0
|
||||
numeral:
|
||||
specifier: ^2.0.6
|
||||
version: 2.0.6
|
||||
@ -1502,6 +1505,10 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
agent-base@7.1.4:
|
||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
@ -1594,6 +1601,9 @@ packages:
|
||||
bidi-js@1.0.3:
|
||||
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
|
||||
|
||||
bignumber.js@9.3.1:
|
||||
resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
|
||||
|
||||
binary-extensions@2.3.0:
|
||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||
engines: {node: '>=8'}
|
||||
@ -1832,6 +1842,10 @@ packages:
|
||||
csstype@3.2.3:
|
||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||
|
||||
data-uri-to-buffer@4.0.1:
|
||||
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
data-urls@7.0.0:
|
||||
resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||
@ -2047,6 +2061,9 @@ packages:
|
||||
resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
extend@3.0.2:
|
||||
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
|
||||
|
||||
fast-glob@3.3.3:
|
||||
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
|
||||
engines: {node: '>=8.6.0'}
|
||||
@ -2082,6 +2099,10 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fetch-blob@3.2.0:
|
||||
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
|
||||
engines: {node: ^12.20 || >= 14.13}
|
||||
|
||||
fflate@0.6.10:
|
||||
resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==}
|
||||
|
||||
@ -2113,6 +2134,10 @@ packages:
|
||||
debug:
|
||||
optional: true
|
||||
|
||||
formdata-polyfill@4.0.10:
|
||||
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
|
||||
forwarded@0.2.0:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@ -2147,6 +2172,14 @@ packages:
|
||||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
gaxios@7.1.4:
|
||||
resolution: {integrity: sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
gcp-metadata@8.1.2:
|
||||
resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
geoip-lite@1.4.10:
|
||||
resolution: {integrity: sha512-4N69uhpS3KFd97m00wiFEefwa+L+HT5xZbzPhwu+sDawStg6UN/dPwWtUfkQuZkGIY1Cj7wDVp80IsqNtGMi2w==}
|
||||
engines: {node: '>=10.3.0'}
|
||||
@ -2189,6 +2222,22 @@ packages:
|
||||
glsl-noise@0.0.0:
|
||||
resolution: {integrity: sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==}
|
||||
|
||||
google-auth-library@10.6.2:
|
||||
resolution: {integrity: sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
google-logging-utils@1.1.3:
|
||||
resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
googleapis-common@8.0.1:
|
||||
resolution: {integrity: sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
googleapis@171.4.0:
|
||||
resolution: {integrity: sha512-xybFL2SmmUgIifgsbsRQYRdNrSAYwxWZDmkZTGjUIaRnX5jPqR8el/cEvo6rCqh7iaZx6MfEPS/lrDgZ0bymkg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
gopd@1.2.0:
|
||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@ -2235,6 +2284,10 @@ packages:
|
||||
resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -2381,6 +2434,9 @@ packages:
|
||||
canvas:
|
||||
optional: true
|
||||
|
||||
json-bigint@1.0.0:
|
||||
resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==}
|
||||
|
||||
jsonfile@3.0.1:
|
||||
resolution: {integrity: sha512-oBko6ZHlubVB5mRFkur5vgYR1UyqX+S6Y/oCfLhqNdcc2fYFlDpIoNc7AfKS1KOGcnNAkvsr0grLck9ANM815w==}
|
||||
|
||||
@ -2732,6 +2788,15 @@ packages:
|
||||
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
node-domexception@1.0.0:
|
||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||
engines: {node: '>=10.5.0'}
|
||||
deprecated: Use your platform's native DOMException instead
|
||||
|
||||
node-fetch@3.3.2:
|
||||
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
node-gyp-build-optional-packages@5.2.2:
|
||||
resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
|
||||
hasBin: true
|
||||
@ -3477,6 +3542,9 @@ packages:
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
|
||||
url-template@2.0.8:
|
||||
resolution: {integrity: sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==}
|
||||
|
||||
use-sync-external-store@1.6.0:
|
||||
resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
|
||||
peerDependencies:
|
||||
@ -3597,6 +3665,10 @@ packages:
|
||||
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
web-streams-polyfill@3.3.3:
|
||||
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
webgl-constants@1.1.1:
|
||||
resolution: {integrity: sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==}
|
||||
|
||||
@ -4635,6 +4707,8 @@ snapshots:
|
||||
|
||||
acorn@7.4.1: {}
|
||||
|
||||
agent-base@7.1.4: {}
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-styles@4.3.0:
|
||||
@ -4707,6 +4781,8 @@ snapshots:
|
||||
dependencies:
|
||||
require-from-string: 2.0.2
|
||||
|
||||
bignumber.js@9.3.1: {}
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
|
||||
block-stream2@2.1.0:
|
||||
@ -5005,6 +5081,8 @@ snapshots:
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
data-uri-to-buffer@4.0.1: {}
|
||||
|
||||
data-urls@7.0.0:
|
||||
dependencies:
|
||||
whatwg-mimetype: 5.0.0
|
||||
@ -5276,6 +5354,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
extend@3.0.2: {}
|
||||
|
||||
fast-glob@3.3.3:
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
@ -5317,6 +5397,11 @@ snapshots:
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.4
|
||||
|
||||
fetch-blob@3.2.0:
|
||||
dependencies:
|
||||
node-domexception: 1.0.0
|
||||
web-streams-polyfill: 3.3.3
|
||||
|
||||
fflate@0.6.10: {}
|
||||
|
||||
fflate@0.8.2: {}
|
||||
@ -5352,6 +5437,10 @@ snapshots:
|
||||
|
||||
follow-redirects@1.16.0: {}
|
||||
|
||||
formdata-polyfill@4.0.10:
|
||||
dependencies:
|
||||
fetch-blob: 3.2.0
|
||||
|
||||
forwarded@0.2.0: {}
|
||||
|
||||
fraction.js@5.3.4: {}
|
||||
@ -5376,6 +5465,22 @@ snapshots:
|
||||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
gaxios@7.1.4:
|
||||
dependencies:
|
||||
extend: 3.0.2
|
||||
https-proxy-agent: 7.0.6
|
||||
node-fetch: 3.3.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
gcp-metadata@8.1.2:
|
||||
dependencies:
|
||||
gaxios: 7.1.4
|
||||
google-logging-utils: 1.1.3
|
||||
json-bigint: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
geoip-lite@1.4.10:
|
||||
dependencies:
|
||||
async: 2.6.4
|
||||
@ -5438,6 +5543,36 @@ snapshots:
|
||||
|
||||
glsl-noise@0.0.0: {}
|
||||
|
||||
google-auth-library@10.6.2:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ecdsa-sig-formatter: 1.0.11
|
||||
gaxios: 7.1.4
|
||||
gcp-metadata: 8.1.2
|
||||
google-logging-utils: 1.1.3
|
||||
jws: 4.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
google-logging-utils@1.1.3: {}
|
||||
|
||||
googleapis-common@8.0.1:
|
||||
dependencies:
|
||||
extend: 3.0.2
|
||||
gaxios: 7.1.4
|
||||
google-auth-library: 10.6.2
|
||||
qs: 6.15.1
|
||||
url-template: 2.0.8
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
googleapis@171.4.0:
|
||||
dependencies:
|
||||
google-auth-library: 10.6.2
|
||||
googleapis-common: 8.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
gopd@1.2.0: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
@ -5488,6 +5623,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
@ -5637,6 +5779,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@noble/hashes'
|
||||
|
||||
json-bigint@1.0.0:
|
||||
dependencies:
|
||||
bignumber.js: 9.3.1
|
||||
|
||||
jsonfile@3.0.1:
|
||||
optionalDependencies:
|
||||
graceful-fs: 4.2.11
|
||||
@ -5956,6 +6102,14 @@ snapshots:
|
||||
|
||||
negotiator@1.0.0: {}
|
||||
|
||||
node-domexception@1.0.0: {}
|
||||
|
||||
node-fetch@3.3.2:
|
||||
dependencies:
|
||||
data-uri-to-buffer: 4.0.1
|
||||
fetch-blob: 3.2.0
|
||||
formdata-polyfill: 4.0.10
|
||||
|
||||
node-gyp-build-optional-packages@5.2.2:
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
@ -6738,6 +6892,8 @@ snapshots:
|
||||
escalade: 3.2.0
|
||||
picocolors: 1.1.1
|
||||
|
||||
url-template@2.0.8: {}
|
||||
|
||||
use-sync-external-store@1.6.0(react@19.2.5):
|
||||
dependencies:
|
||||
react: 19.2.5
|
||||
@ -6803,6 +6959,8 @@ snapshots:
|
||||
dependencies:
|
||||
xml-name-validator: 5.0.0
|
||||
|
||||
web-streams-polyfill@3.3.3: {}
|
||||
|
||||
webgl-constants@1.1.1: {}
|
||||
|
||||
webgl-sdf-generator@1.1.1: {}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user