Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Generate text and images via the reverse-engineered Gemini Web API with multi-turn conversation support.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/gemini-webapi/components/gem-mixin.ts
1import { GRPC } from '../constants.js';2import { APIError } from '../exceptions.js';3import { Gem, GemJar, RPCData } from '../types/index.js';4import { logger } from '../utils/logger.js';5import { extract_json_from_response, get_nested_value } from '../utils/parsing.js';67export abstract class GemMixin {8protected _gems: GemJar | null = null;910protected abstract _run<T>(fn: () => Promise<T>, retry: number): Promise<T>;11protected abstract _batch_execute(payloads: RPCData[], opts?: RequestInit): Promise<Response>;12protected abstract close(delay?: number): Promise<void>;1314get gems(): GemJar {15if (this._gems == null) {16throw new Error(17'Gems not fetched yet. Call `GeminiClient.fetch_gems()` method to fetch gems from gemini.google.com.',18);19}20return this._gems;21}2223async fetch_gems(include_hidden: boolean = false, opts?: RequestInit): Promise<GemJar> {24return await this._run(async () => {25const res = await this._batch_execute(26[27new RPCData(GRPC.LIST_GEMS, include_hidden ? '[4]' : '[3]', 'system'),28new RPCData(GRPC.LIST_GEMS, '[2]', 'custom'),29],30opts,31);3233let response_json: unknown;34try {35response_json = extract_json_from_response(await res.text());36if (!Array.isArray(response_json)) throw new Error('Invalid response');37} catch {38await this.close();39throw new APIError('Failed to fetch gems. Invalid response data received. Client will try to re-initialize on next request.');40}4142let predefined: unknown[] = [];43let custom: unknown[] = [];4445try {46for (const part of response_json as unknown[]) {47if (!Array.isArray(part)) continue;48const ident = part[part.length - 1];49const body = get_nested_value<string | null>(part, [2], null);50if (!body) continue;5152if (ident === 'system') {53const parsed = JSON.parse(body) as unknown[];54predefined = (Array.isArray(parsed) ? (parsed[2] as unknown[]) : []) ?? [];55} else if (ident === 'custom') {56const parsed = JSON.parse(body) as unknown[] | null;57if (parsed) custom = (parsed[2] as unknown[]) ?? [];58}59}6061if (predefined.length === 0 && custom.length === 0) throw new Error('No gems');62} catch {63await this.close();64logger.debug('Invalid response while parsing gems');65throw new APIError('Failed to fetch gems. Invalid response data received. Client will try to re-initialize on next request.');66}6768const entries: [string, Gem][] = [];6970for (const gem of predefined) {71if (!Array.isArray(gem)) continue;72const id = String(get_nested_value(gem, [0], ''));73if (!id) continue;74entries.push([75id,76new Gem(77id,78String(get_nested_value(gem, [1, 0], '')),79get_nested_value<string | null>(gem, [1, 1], null),80get_nested_value<string | null>(gem, [2, 0], null),81true,82),83]);84}8586for (const gem of custom) {87if (!Array.isArray(gem)) continue;88const id = String(get_nested_value(gem, [0], ''));89if (!id) continue;90entries.push([91id,92new Gem(93id,94String(get_nested_value(gem, [1, 0], '')),95get_nested_value<string | null>(gem, [1, 1], null),96get_nested_value<string | null>(gem, [2, 0], null),97false,98),99]);100}101102this._gems = new GemJar(entries);103return this._gems;104}, 2);105}106107async create_gem(name: string, prompt: string, description: string = ''): Promise<Gem> {108return await this._run(async () => {109const payload = JSON.stringify([110[111name,112description,113prompt,114null,115null,116null,117null,118null,1190,120null,1211,122null,123null,124null,125[],126],127]);128129const res = await this._batch_execute([new RPCData(GRPC.CREATE_GEM, payload)]);130try {131const response_json = extract_json_from_response(await res.text()) as unknown[];132const gem_id = JSON.parse(String((response_json[0] as unknown[])[2]))[0] as string;133return new Gem(gem_id, name, description, prompt, false);134} catch {135await this.close();136throw new APIError('Failed to create gem. Invalid response data received. Client will try to re-initialize on next request.');137}138}, 2);139}140141async update_gem(gem: Gem | string, name: string, prompt: string, description: string = ''): Promise<Gem> {142return await this._run(async () => {143const gem_id = typeof gem === 'string' ? gem : gem.id;144const payload = JSON.stringify([145gem_id,146[147name,148description,149prompt,150null,151null,152null,153null,154null,1550,156null,1571,158null,159null,160null,161[],1620,163],164]);165166await this._batch_execute([new RPCData(GRPC.UPDATE_GEM, payload)]);167return new Gem(gem_id, name, description, prompt, false);168}, 2);169}170171async delete_gem(gem: Gem | string, opts?: RequestInit): Promise<void> {172return await this._run(async () => {173const gem_id = typeof gem === 'string' ? gem : gem.id;174const payload = JSON.stringify([gem_id]);175await this._batch_execute([new RPCData(GRPC.DELETE_GEM, payload)], opts);176}, 2);177}178}179180