@mushinjs/plugin-providers 0.0.0-stage → 0.2.0-next.20261113103537
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/dist/index.d.ts +188 -0
- package/dist/index.js +751 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +273 -0
- package/package.json +49 -3
- package/README.md +0 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { Context, isMasked, mask, MushinError, randomId, Service, toJsonSchema, usage, z } from '@mushinjs/core'\nimport type {} from '@mushinjs/plugin-storage'\nimport type {\n ChatRequest, DecisionQuestion, LlmChatOptions, LlmMessage, LlmDecideOptions, LlmDecideResult, LlmEmbedOptions, LlmEmbedResult, LlmResult, ModelProfile,\n ProviderCapability, ProviderClient, ProviderRecord, ProviderTest, ProviderType, StreamEvent,\n} from './types'\n\nexport * from './types'\n\ndeclare module '@mushinjs/core' {\n interface Context {\n llm: LlmService\n }\n\n interface Events {\n 'providers/changed'(): void\n 'llm/called'(call: LlmCall): void\n }\n}\n\n/** One finished model request, for usage statistics. */\nexport interface LlmCall {\n provider: string\n providerName: string\n model: string\n ok: boolean\n inputTokens?: number\n outputTokens?: number\n latencyMs: number\n}\n\nconst STORAGE_KEY = 'providers'\n\n/** A chat request that shows the model an image is counted as reading it. */\nfunction chatKind(request: { messages: LlmMessage[] }) {\n return request.messages.some((message) => message.role === 'user' && Array.isArray(message.content) && message.content.some((part) => part.type === 'image')) ? 'vision' : 'chat'\n}\n\n/** Most texts sent in one embedding request. */\nexport const EMBED_BATCH = 64\n\ninterface StoredProviders {\n providers: ProviderRecord[]\n profiles?: ModelProfile[]\n}\n\n/** A provider and model to send a request to; a profile resolves to several, tried in order. */\ninterface Target {\n record: ProviderRecord\n model: string\n}\n\nconst providerInput = z.object({\n name: z.string().min(1).describe('Display name, such as \"DeepSeek\"'),\n type: z.string().describe('Provider type id from providers.types, such as \"openai\" or \"anthropic\"'),\n config: z.record(z.string(), z.any()).describe('Config matching the type\\'s schema from providers.types'),\n models: z.array(z.string()).optional().describe('Models offered by this provider'),\n defaultModel: z.string().optional().describe('Model used when a request names none; any name the provider accepts, listed or not. Empty clears it'),\n isDefault: z.boolean().optional().describe('Make this the default provider'),\n})\n\nconst profileInput = z.object({\n name: z.string().trim().min(1).describe('Display name, such as \"Chat with fallbacks\"'),\n models: z.array(z.object({\n provider: z.string().describe('Provider id from providers.list'),\n model: z.string().trim().min(1).describe('Model name; any name the provider accepts, listed or not'),\n })).min(1).describe('The first model answers; each later one is tried when the one before it fails'),\n})\n\nconst decisionText = z.union([z.string(), z.record(z.string(), z.unknown()), z.array(z.unknown()), z.null()])\n\nconst questionInput = z.discriminatedUnion('type', [\n z.object({\n type: z.literal('noul'),\n instructions: decisionText.optional(),\n criteria: z.object({ true: decisionText.optional(), false: decisionText.optional() }).optional().describe('What true and false mean'),\n }),\n z.object({\n type: z.literal('choice'),\n instructions: decisionText.optional(),\n criteria: z.record(z.string(), decisionText)\n .refine((value) => { const count = Object.keys(value).length; return count >= 1 && count <= 255 }, 'Give 1 to 255 options')\n .describe('Option name to description (1 to 255 options; null to go by the name alone)'),\n }),\n z.object({\n type: z.literal('score'),\n instructions: decisionText.optional(),\n criteria: z.array(decisionText).min(1).max(10).describe('1 to 10 ordered levels; the index is the score'),\n }),\n z.object({ type: z.literal('box'), instructions: decisionText.optional() }),\n]) satisfies z.ZodType<DecisionQuestion>\n\nconst decideInput = z.object({\n provider: z.string().optional().describe('Provider id of a decision provider; the default decision provider when omitted'),\n model: z.string().optional().describe('Model name; the provider default model when omitted'),\n state: z.union([z.string(), z.record(z.string(), z.unknown()), z.array(z.unknown())]).describe('What to decide about: text or any JSON value'),\n questions: z.record(z.string(), questionInput).describe('Questions by name; each answer comes back under the same name'),\n assets: z.record(z.string(), z.object({\n type: z.literal('image'),\n format: z.enum(['png', 'jpg', 'jpeg']),\n data: z.string().describe('Base64 image data'),\n })).optional().describe('Images the state refers to by placeholder name'),\n})\n\n/** The question a decision-only provider answers in its connection test. */\nconst TEST_DECISION = {\n state: 'The sky on a clear day is blue.',\n questions: { ok: { type: 'noul', instructions: 'Is the statement true?' } },\n} satisfies Omit<LlmDecideOptions, 'provider' | 'model'>\n\n/**\n * Owns provider records and exposes one `chat` entry point.\n * Provider types (OpenAI-compatible, Anthropic, ...) are registered by other plugins.\n */\nexport class LlmService extends Service {\n static inject = ['storage', 'actions']\n\n private home: Context\n private typeStore = new Map<string, ProviderType>()\n private records: ProviderRecord[] = []\n private profileRecords: ModelProfile[] = []\n private clients = new Map<string, { key: string; client: ProviderClient }>()\n\n constructor(ctx: Context) {\n super(ctx, 'llm')\n this.home = ctx\n }\n\n /** Provider lifecycle lines, tagged `provider` in the dashboard. Never includes config values. */\n private get logger() {\n return this.home.logger('provider')\n }\n\n async *[Service.init]() {\n const stored = await this.home.storage.get<StoredProviders>(STORAGE_KEY)\n this.records = stored?.providers ?? []\n this.profileRecords = stored?.profiles ?? []\n this.logger.info('loaded %d provider(s)', this.records.length)\n yield* this.defineActions()\n }\n\n /** Registers a provider type. It is removed when the calling plugin unloads. */\n defineType<C>(type: ProviderType<C>) {\n const ctx = this.ctx\n return ctx.effect(() => {\n if (this.typeStore.has(type.id)) throw new Error(`provider type \"${type.id}\" is already defined`)\n this.typeStore.set(type.id, type)\n this.logger.info('provider type %s is available', type.id)\n ctx.emit('providers/changed')\n return () => {\n this.typeStore.delete(type.id)\n this.logger.info('provider type %s was removed', type.id)\n for (const record of this.records) {\n if (record.type === type.id) this.clients.delete(record.id)\n }\n ctx.emit('providers/changed')\n }\n }, `llm.defineType(${type.id})`)\n }\n\n types() {\n return [...this.typeStore.values()].map((type) => ({\n id: type.id,\n displayName: type.displayName,\n configSchema: toJsonSchema(type.config),\n secrets: type.secrets ?? [],\n presets: type.presets ?? [],\n capabilities: type.capabilities ?? ['chat'],\n }))\n }\n\n list() {\n return this.records.map((record) => this.redact(record))\n }\n\n get(id: string) {\n const record = this.records.find((item) => item.id === id)\n if (!record) throw new MushinError('not_found', `No provider with id \"${id}\"`)\n return record\n }\n\n /** What a saved provider can do. A record whose type is not loaded counts as a chat provider. */\n capabilities(record: ProviderRecord): ProviderCapability[] {\n return this.typeStore.get(record.type)?.capabilities ?? ['chat']\n }\n\n supports(record: ProviderRecord, capability: ProviderCapability) {\n return this.capabilities(record).includes(capability)\n }\n\n /** The default provider that can do `capability`, or the first such one if none is marked default. */\n defaultProvider(capability: ProviderCapability = 'chat') {\n const able = this.records.filter((record) => this.supports(record, capability))\n return able.find((record) => record.isDefault) ?? able[0]\n }\n\n /** True when at least one chat provider has passed its connection test. */\n isReady() {\n return this.records.some((record) => record.lastTest?.ok && this.supports(record, 'chat'))\n }\n\n private redact(record: ProviderRecord) {\n const secrets = this.typeStore.get(record.type)?.secrets ?? []\n const config = { ...record.config }\n for (const key of secrets) {\n if (typeof config[key] === 'string') config[key] = mask(config[key])\n }\n return { ...record, config, typeAvailable: this.typeStore.has(record.type), capabilities: this.capabilities(record) }\n }\n\n private requireType(id: string) {\n const type = this.typeStore.get(id)\n if (!type) {\n const known = [...this.typeStore.keys()].join(', ') || 'none'\n throw new MushinError('invalid_input', `Unknown provider type \"${id}\". Available types: ${known}`)\n }\n return type\n }\n\n private validateConfig(type: ProviderType, config: unknown) {\n const result = type.config.safeParse(config)\n if (!result.success) throw new MushinError('invalid_input', z.prettifyError(result.error), result.error.issues)\n return result.data as Record<string, any>\n }\n\n private async save() {\n await this.home.storage.set(STORAGE_KEY, { providers: this.records, profiles: this.profileRecords } satisfies StoredProviders)\n this.home.emit('providers/changed')\n }\n\n async create(input: z.infer<typeof providerInput>) {\n const type = this.requireType(input.type)\n const record: ProviderRecord = {\n id: randomId(),\n name: input.name,\n type: input.type,\n config: this.validateConfig(type, input.config),\n models: input.models ?? [],\n defaultModel: input.defaultModel || input.models?.[0],\n createdAt: Date.now(),\n }\n // The first chat provider becomes the default; a decision-only one never takes it on its own.\n const chat = type.capabilities?.includes('chat') ?? true\n if (input.isDefault || (chat && !this.defaultProvider('chat'))) this.markDefault(record)\n this.records.push(record)\n await this.save()\n this.logger.info('created provider %s', this.label(record))\n return this.redact(record)\n }\n\n async update(id: string, patch: Partial<z.infer<typeof providerInput>>) {\n const record = this.get(id)\n const type = this.requireType(patch.type ?? record.type)\n if (patch.config) {\n // Masked values come back unchanged from the dashboard; keep the stored secret.\n const merged = { ...patch.config }\n for (const key of type.secrets ?? []) {\n if (isMasked(merged[key])) merged[key] = record.config[key]\n }\n record.config = this.validateConfig(type, merged)\n }\n if (patch.name !== undefined) record.name = patch.name\n if (patch.type !== undefined) record.type = patch.type\n if (patch.models !== undefined) record.models = patch.models\n if (patch.defaultModel !== undefined) record.defaultModel = patch.defaultModel || undefined\n if (patch.isDefault) this.markDefault(record)\n record.lastTest = undefined\n this.clients.delete(id)\n await this.save()\n this.logger.info('updated provider %s', this.label(record))\n return this.redact(record)\n }\n\n async delete(id: string) {\n const record = this.get(id)\n this.records = this.records.filter((item) => item !== record)\n const next = this.defaultProvider('chat') ?? this.records[0]\n if (record.isDefault && next) this.markDefault(next)\n this.clients.delete(id)\n // Profiles keep their other models; a profile left with none stays so its users see why it fails.\n for (const profile of this.profileRecords) profile.models = profile.models.filter((item) => item.provider !== id)\n await this.save()\n this.logger.info('deleted provider %s', this.label(record))\n return { deleted: id }\n }\n\n private markDefault(record: ProviderRecord) {\n for (const item of this.records) item.isDefault = false\n record.isDefault = true\n }\n\n /** Log lines name a provider by its id; the dashboard links it to the provider. */\n private label(record: ProviderRecord) {\n return record.id\n }\n\n async setDefault(id: string) {\n this.markDefault(this.get(id))\n await this.save()\n this.logger.info('default provider is now %s', this.label(this.get(id)))\n return this.redact(this.get(id))\n }\n\n profiles() {\n return this.profileRecords\n }\n\n getProfile(id: string) {\n const profile = this.profileRecords.find((item) => item.id === id)\n if (!profile) throw new MushinError('not_found', `No profile with id \"${id}\"`)\n return profile\n }\n\n isProfile(id: string) {\n return this.profileRecords.some((item) => item.id === id)\n }\n\n private checkProfileModels(models: z.infer<typeof profileInput>['models']) {\n for (const item of models) {\n const record = this.get(item.provider)\n if (!this.supports(record, 'chat')) throw new MushinError('invalid_input', `Provider \"${record.name}\" does not chat, so it cannot be in a profile`)\n }\n return models.map((item) => ({ provider: item.provider, model: item.model.trim() }))\n }\n\n async createProfile(input: z.infer<typeof profileInput>) {\n const profile: ModelProfile = { id: randomId(), name: input.name, models: this.checkProfileModels(input.models), createdAt: Date.now() }\n this.profileRecords.push(profile)\n await this.save()\n this.logger.info('created profile %s with %d model(s)', profile.id, profile.models.length)\n return profile\n }\n\n async updateProfile(id: string, patch: Partial<z.infer<typeof profileInput>>) {\n const profile = this.getProfile(id)\n if (patch.name !== undefined) profile.name = patch.name\n if (patch.models !== undefined) profile.models = this.checkProfileModels(patch.models)\n await this.save()\n this.logger.info('updated profile %s', profile.id)\n return profile\n }\n\n async deleteProfile(id: string) {\n const profile = this.getProfile(id)\n this.profileRecords = this.profileRecords.filter((item) => item !== profile)\n await this.save()\n this.logger.info('deleted profile %s', profile.id)\n return { deleted: id }\n }\n\n /** Providers and profiles as `{ value, label }`, for fields that take either. */\n choices() {\n return [\n ...this.records.filter((record) => this.supports(record, 'chat')).map((record) => ({ value: record.id, label: record.name, group: 'providers' })),\n ...this.profileRecords.map((profile) => ({ value: profile.id, label: profile.name, group: 'profiles' })),\n ]\n }\n\n client(record: ProviderRecord) {\n const type = this.typeStore.get(record.type)\n if (!type) throw new MushinError('unavailable', `Provider type \"${record.type}\" is not loaded; enable its plugin`)\n const key = JSON.stringify(record.config)\n const cached = this.clients.get(record.id)\n if (cached?.key === key) return cached.client\n const client = type.create(record.config)\n this.logger.debug('created a client for provider %s', this.label(record))\n this.clients.set(record.id, { key, client })\n return client\n }\n\n private resolve(options: { provider?: string; model?: string }, capability: ProviderCapability = 'chat') {\n const record = options.provider ? this.get(options.provider) : this.defaultProvider(capability)\n if (!record) {\n throw new MushinError('unavailable', capability === 'chat'\n ? 'No LLM provider is configured. Add one in Providers first.'\n : 'No decision provider is configured. Add one in Providers first.')\n }\n if (!this.supports(record, capability)) {\n throw new MushinError('invalid_input', capability === 'chat'\n ? `Provider \"${record.name}\" only answers decisions; use providers.decide`\n : `Provider \"${record.name}\" cannot answer decisions`)\n }\n const model = options.model ?? record.defaultModel ?? record.models[0]\n if (!model) throw new MushinError('invalid_input', `Provider \"${record.name}\" has no model set`)\n return { record, model, client: this.client(record) }\n }\n\n /** Where a request goes: one provider and model, or a profile's models in order. */\n private targets(options: { provider?: string; model?: string }): Target[] {\n const profile = options.provider ? this.profileRecords.find((item) => item.id === options.provider) : undefined\n if (!profile) {\n const { record, model } = this.resolve(options)\n return [{ record, model }]\n }\n const targets = profile.models.flatMap((item) => {\n const record = this.records.find((provider) => provider.id === item.provider)\n return record ? [{ record, model: item.model }] : []\n })\n if (!targets.length) throw new MushinError('invalid_input', `Profile \"${profile.name}\" has no models whose provider still exists`)\n return targets\n }\n\n /** Logs a failed model of a profile and says whether to try the next one. */\n private fallBack(targets: Target[], index: number, error: unknown, signal?: AbortSignal) {\n if (signal?.aborted || index === targets.length - 1) return false\n const { record, model } = targets[index]\n const next = targets[index + 1]\n this.logger.warn('falling back from %s (%s) to %s (%s): %s', record.id, model, next.record.id, next.model, error instanceof Error ? error.message : error)\n return true\n }\n\n /** Counts a request against the message being handled, if any; see `usage` in core. */\n private report(kind: string, record: ProviderRecord, model: string, latencyMs: number, used?: { inputTokens?: number; outputTokens?: number }, ok = true) {\n usage.report({\n kind,\n plugin: this.ctx.fiber.name,\n provider: record.name,\n model,\n latencyMs,\n ok,\n ...(used?.inputTokens !== undefined ? { inputTokens: used.inputTokens } : {}),\n ...(used?.outputTokens !== undefined ? { outputTokens: used.outputTokens } : {}),\n })\n }\n\n private result(record: ProviderRecord, model: string, start: number, response: Awaited<ReturnType<ProviderClient['chat']>>, request: { messages: LlmMessage[] }): LlmResult {\n const latencyMs = Date.now() - start\n const tokens = response.usage ? response.usage.inputTokens + response.usage.outputTokens : undefined\n this.report(chatKind(request), record, model, latencyMs, response.usage)\n this.home.emit('llm/called', {\n provider: record.id,\n providerName: record.name,\n model,\n ok: true,\n inputTokens: response.usage?.inputTokens,\n outputTokens: response.usage?.outputTokens,\n latencyMs,\n })\n return {\n ...response,\n provider: record.id,\n providerName: record.name,\n model,\n latencyMs,\n source: { kind: 'llm', provider: record.name, model, latencyMs, tokens },\n }\n }\n\n /** Runs one chat completion through the chosen (or default) provider. */\n async chat(options: LlmChatOptions): Promise<LlmResult> {\n const targets = this.targets(options)\n for (let index = 0; ; index++) {\n const { record, model } = targets[index]\n const start = Date.now()\n try {\n const response = await this.client(record).chat({ ...options, model } satisfies ChatRequest)\n return this.result(record, model, start, response, options)\n } catch (error) {\n this.logRequestError(record, model, error, start, chatKind(options))\n if (!this.fallBack(targets, index, error, options.signal)) throw error\n }\n }\n }\n\n /** Streams text deltas, then yields the final result. Falls back to `chat` for clients without streaming. */\n async *stream(options: LlmChatOptions): AsyncGenerator<StreamEvent | { type: 'result'; result: LlmResult }> {\n const targets = this.targets(options)\n for (let index = 0; ; index++) {\n const { record, model } = targets[index]\n const start = Date.now()\n const request: ChatRequest = { ...options, model }\n // Once text has reached the caller, a fallback would repeat it, so only a silent failure falls back.\n let streamed = false\n try {\n const client = this.client(record)\n if (!client.stream) {\n const response = await client.chat(request)\n if (response.text) yield { type: 'delta', text: response.text }\n yield { type: 'result', result: this.result(record, model, start, response, request) }\n return\n }\n for await (const event of client.stream(request)) {\n if (event.type === 'delta') {\n streamed = true\n yield event\n } else yield { type: 'result', result: this.result(record, model, start, event.response, request) }\n }\n return\n } catch (error) {\n this.logRequestError(record, model, error, start, chatKind(request))\n if (streamed || !this.fallBack(targets, index, error, options.signal)) throw error\n }\n }\n }\n\n /** Whether a provider can embed text, for pickers. */\n canEmbed(id: string) {\n try {\n return !!this.client(this.get(id)).embed\n } catch {\n return false\n }\n }\n\n /** Embeds texts through a provider, `EMBED_BATCH` at a time; each batch is retried once. */\n async embed(options: LlmEmbedOptions): Promise<LlmEmbedResult> {\n const record = options.provider ? this.get(options.provider) : this.defaultProvider('chat')\n if (!record) throw new MushinError('unavailable', 'No LLM provider is configured. Add one in Providers first.')\n const client = this.client(record)\n if (!client.embed) throw new MushinError('unavailable', `Provider \"${record.name}\" cannot embed text`)\n const vectors: number[][] = []\n let tokens = 0\n const start = Date.now()\n for (let index = 0; index < options.texts.length; index += EMBED_BATCH) {\n const input = options.texts.slice(index, index + EMBED_BATCH)\n let response\n try {\n response = await client.embed({ model: options.model, input, signal: options.signal }).catch((error) => {\n if (options.signal?.aborted) throw error\n return client.embed!({ model: options.model, input, signal: options.signal })\n })\n } catch (error) {\n this.logRequestError(record, options.model, error, start, 'embed')\n throw error\n }\n if (response.vectors.length !== input.length) throw new MushinError('unavailable', `Provider \"${record.name}\" returned ${response.vectors.length} vectors for ${input.length} texts`)\n vectors.push(...response.vectors)\n tokens += response.usage?.inputTokens ?? 0\n }\n this.home.emit('llm/called', { provider: record.id, providerName: record.name, model: options.model, ok: true, inputTokens: tokens || undefined, latencyMs: Date.now() - start })\n this.report('embed', record, options.model, Date.now() - start, tokens ? { inputTokens: tokens } : undefined)\n return { vectors, provider: record.id, model: options.model, dimension: vectors[0]?.length ?? 0, ...(tokens ? { tokens } : {}) }\n }\n\n /** Asks a decision provider structured questions about a state. */\n async decide(options: LlmDecideOptions): Promise<LlmDecideResult> {\n const { record, model, client } = this.resolve(options, 'decision')\n if (!client.decide) throw new MushinError('unavailable', `Provider \"${record.name}\" cannot answer decisions`)\n const start = Date.now()\n let response\n try {\n response = await client.decide({ model, state: options.state, questions: options.questions, assets: options.assets, signal: options.signal })\n } catch (error) {\n this.logRequestError(record, model, error, start, 'decision')\n throw error\n }\n const latencyMs = Date.now() - start\n this.report('decision', record, model, latencyMs, response.usage)\n this.home.emit('llm/called', {\n provider: record.id,\n providerName: record.name,\n model,\n ok: true,\n inputTokens: response.usage?.inputTokens,\n outputTokens: response.usage?.outputTokens,\n latencyMs,\n })\n return { ...response, provider: record.id, providerName: record.name, model, latencyMs }\n }\n\n private logRequestError(record: ProviderRecord, model: string, error: unknown, start: number, kind: string) {\n // A caller that gave up (timeout, user stop) is not the provider's fault.\n if (error instanceof Error && error.name === 'AbortError') return\n this.report(kind, record, model, Date.now() - start, undefined, false)\n this.home.emit('llm/called', { provider: record.id, providerName: record.name, model, ok: false, latencyMs: Date.now() - start })\n this.logger.warn('request to %s (%s) failed: %s', this.label(record), model, error instanceof Error ? error.message : error)\n }\n\n async test(id: string): Promise<ProviderTest> {\n const record = this.get(id)\n const start = Date.now()\n let test: ProviderTest\n try {\n // A decision-only provider answers a small true-or-false question instead of chatting.\n const capability = this.supports(record, 'chat') ? 'chat' : 'decision'\n const { model, client } = this.resolve({ provider: id }, capability)\n const signal = AbortSignal.timeout(60_000)\n if (capability === 'chat') {\n await client.chat({ model, messages: [{ role: 'user', content: 'Reply with OK.' }], maxTokens: 256, signal })\n } else {\n if (!client.decide) throw new MushinError('unavailable', `Provider \"${record.name}\" cannot answer decisions`)\n const response = await client.decide({ model, ...TEST_DECISION, signal })\n if (response.answers.ok?.type !== 'noul') throw new MushinError('unavailable', 'The provider did not answer the test question')\n }\n test = { ok: true, at: Date.now(), latencyMs: Date.now() - start, model }\n } catch (error) {\n test = { ok: false, at: Date.now(), error: error instanceof Error ? error.message : String(error) }\n }\n record.lastTest = test\n await this.save()\n if (test.ok) this.logger.info('provider %s passed its test in %d ms', this.label(record), test.latencyMs)\n else this.logger.warn('provider %s failed its test: %s', this.label(record), test.error)\n return test\n }\n\n async models(input: { provider?: string; type?: string; config?: Record<string, any> }) {\n let client: ProviderClient\n if (input.provider) {\n client = this.client(this.get(input.provider))\n } else if (input.type && input.config) {\n client = this.requireType(input.type).create(input.config)\n } else {\n throw new MushinError('invalid_input', 'Pass a provider id, or a type with its config')\n }\n if (!client.listModels) throw new MushinError('unavailable', 'This provider type cannot list models')\n return client.listModels(AbortSignal.timeout(15_000))\n }\n\n private *defineActions() {\n const actions = this.home.actions\n const idInput = z.object({ id: z.string().describe('Provider id from providers.list') })\n\n yield actions.define({\n name: 'providers.types',\n description: 'List provider types with their config JSON Schemas and presets (base URLs for DeepSeek, Moonshot, Qwen and others).',\n risk: 'read',\n input: z.object({}),\n handler: () => this.types(),\n })\n\n yield actions.define({\n name: 'providers.list',\n description: 'List configured LLM providers and model profiles. Secrets are masked. Also reports whether the Agent is ready (a provider passed its test).',\n risk: 'read',\n input: z.object({}),\n handler: () => ({ providers: this.list(), profiles: this.profiles(), ready: this.isReady() }),\n })\n\n yield actions.define({\n name: 'providers.choices',\n description: 'Providers and model profiles as ids and names, for fields that take a provider id. A profile id works wherever a provider id does.',\n risk: 'read',\n input: z.object({}),\n handler: () => this.choices(),\n })\n\n yield actions.define({\n name: 'providers.createProfile',\n description: 'Add a model profile: a primary model and fallbacks, each a provider id and a model name. Requests through the profile try them in order until one answers. Use the profile id wherever a provider id is taken.',\n risk: 'write',\n input: profileInput,\n handler: (input) => this.createProfile(input),\n })\n\n yield actions.define({\n name: 'providers.updateProfile',\n description: 'Rename a model profile or replace its ordered model list.',\n risk: 'write',\n input: z.object({ id: z.string().describe('Profile id from providers.list') }).extend(profileInput.partial().shape),\n handler: ({ id, ...patch }) => this.updateProfile(id, patch),\n })\n\n yield actions.define({\n name: 'providers.deleteProfile',\n description: 'Delete a model profile. Places that used it fall back to the default provider.',\n risk: 'dangerous',\n input: z.object({ id: z.string().describe('Profile id from providers.list') }),\n handler: ({ id }) => this.deleteProfile(id),\n })\n\n yield actions.define({\n name: 'providers.create',\n description: 'Add an LLM provider. Run providers.test afterwards to check it works.',\n risk: 'write',\n input: providerInput,\n handler: (input) => this.create(input),\n })\n\n yield actions.define({\n name: 'providers.update',\n description: 'Change a provider. Masked secret values are kept as they are.',\n risk: 'write',\n input: idInput.extend(providerInput.partial().shape),\n handler: ({ id, ...patch }) => this.update(id, patch),\n })\n\n yield actions.define({\n name: 'providers.delete',\n description: 'Delete a provider.',\n risk: 'dangerous',\n input: idInput,\n handler: ({ id }) => this.delete(id),\n })\n\n yield actions.define({\n name: 'providers.test',\n description: 'Send a tiny request through a provider and report latency or the error.',\n risk: 'write',\n input: idInput,\n handler: ({ id }) => this.test(id),\n })\n\n yield actions.define({\n name: 'providers.setDefault',\n description: 'Make a provider the default for the Agent and bot plugins.',\n risk: 'write',\n input: idInput,\n handler: ({ id }) => this.setDefault(id),\n })\n\n yield actions.define({\n name: 'providers.decide',\n description: 'Ask a decision provider (such as TypeSafe Jev) structured questions about a state: noul (probability a statement holds), choice (pick a named option), score (grade on an ordered scale) or box (locate something in an image asset; edges come back as 0-1 fractions of the image). Instructions and criteria may be text or JSON. Returns one answer per question name.',\n risk: 'read',\n input: decideInput,\n handler: (input) => this.decide(input),\n })\n\n yield actions.define({\n name: 'providers.decideChoices',\n description: 'Providers that answer decisions, as ids and names.',\n risk: 'read',\n input: z.object({}),\n handler: () => this.records.filter((record) => this.supports(record, 'decision')).map((record) => ({ value: record.id, label: record.name })),\n })\n\n yield actions.define({\n name: 'providers.embedChoices',\n description: 'Providers that can embed text (for knowledge bases), as ids and names.',\n risk: 'read',\n input: z.object({}),\n handler: () => this.records.filter((record) => this.canEmbed(record.id)).map((record) => ({ value: record.id, label: record.name })),\n })\n\n yield actions.define({\n name: 'providers.models',\n description: 'Fetch the live model list from a saved provider, or from an unsaved type and config.',\n risk: 'read',\n input: z.object({\n provider: z.string().optional(),\n type: z.string().optional(),\n config: z.record(z.string(), z.any()).optional(),\n }),\n handler: (input) => this.models(input),\n })\n }\n}\n\nexport default LlmService\n"],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { JsonSchema, LocalizedText, Provenance, z } from "@mushinjs/core";
|
|
2
|
+
export interface ToolCall {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
/** JSON-encoded arguments, exactly as the model produced them. */
|
|
6
|
+
arguments: string;
|
|
7
|
+
}
|
|
8
|
+
export type ContentPart = {
|
|
9
|
+
type: 'text';
|
|
10
|
+
text: string;
|
|
11
|
+
} | {
|
|
12
|
+
type: 'image';
|
|
13
|
+
url: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* A provider's own representation of an assistant turn. Clients of the same provider type
|
|
17
|
+
* replay it verbatim, which keeps reasoning blocks valid across a tool loop.
|
|
18
|
+
*/
|
|
19
|
+
export interface RawTurn {
|
|
20
|
+
type: string;
|
|
21
|
+
content: unknown;
|
|
22
|
+
}
|
|
23
|
+
/** Vendor-neutral chat message. Provider clients convert to and from their SDK formats. */
|
|
24
|
+
export type LlmMessage = {
|
|
25
|
+
role: 'system';
|
|
26
|
+
content: string;
|
|
27
|
+
} | {
|
|
28
|
+
role: 'user';
|
|
29
|
+
content: string | ContentPart[];
|
|
30
|
+
} | {
|
|
31
|
+
role: 'assistant';
|
|
32
|
+
content: string;
|
|
33
|
+
toolCalls?: ToolCall[];
|
|
34
|
+
raw?: RawTurn;
|
|
35
|
+
} | {
|
|
36
|
+
role: 'tool';
|
|
37
|
+
toolCallId: string;
|
|
38
|
+
content: string;
|
|
39
|
+
isError?: boolean;
|
|
40
|
+
};
|
|
41
|
+
export interface ToolDefinition {
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
parameters: JsonSchema;
|
|
45
|
+
}
|
|
46
|
+
export interface ChatRequest {
|
|
47
|
+
model: string;
|
|
48
|
+
messages: LlmMessage[];
|
|
49
|
+
tools?: ToolDefinition[];
|
|
50
|
+
maxTokens?: number;
|
|
51
|
+
temperature?: number;
|
|
52
|
+
signal?: AbortSignal;
|
|
53
|
+
}
|
|
54
|
+
export interface Usage {
|
|
55
|
+
inputTokens: number;
|
|
56
|
+
outputTokens: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Normalized stop reasons. `length` means the output hit the token limit, so any tool call in it
|
|
60
|
+
* may be truncated and must not run; `refusal` means the model declined.
|
|
61
|
+
*/
|
|
62
|
+
export type FinishReason = 'stop' | 'tool_calls' | 'length' | 'refusal' | 'other';
|
|
63
|
+
export interface ChatResponse {
|
|
64
|
+
text: string;
|
|
65
|
+
toolCalls: ToolCall[];
|
|
66
|
+
usage?: Usage;
|
|
67
|
+
finishReason?: FinishReason;
|
|
68
|
+
/** Native turn to replay on the next request; see {@link RawTurn}. */
|
|
69
|
+
raw?: RawTurn;
|
|
70
|
+
}
|
|
71
|
+
export type StreamEvent = {
|
|
72
|
+
type: 'delta';
|
|
73
|
+
text: string;
|
|
74
|
+
} | {
|
|
75
|
+
type: 'done';
|
|
76
|
+
response: ChatResponse;
|
|
77
|
+
};
|
|
78
|
+
export interface EmbedRequest {
|
|
79
|
+
model: string;
|
|
80
|
+
/** Texts to embed; `LlmService.embed()` sends at most `EMBED_BATCH` at a time. */
|
|
81
|
+
input: string[];
|
|
82
|
+
signal?: AbortSignal;
|
|
83
|
+
}
|
|
84
|
+
export interface EmbedResponse {
|
|
85
|
+
/** One vector per input, in order. */
|
|
86
|
+
vectors: number[][];
|
|
87
|
+
usage?: {
|
|
88
|
+
inputTokens: number;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* A question for a decision model. `noul` asks for a probability that a statement holds;
|
|
93
|
+
* `choice` picks one of the named options; `score` grades on an ordered scale whose index is the
|
|
94
|
+
* score; `box` locates something in an image asset.
|
|
95
|
+
*/
|
|
96
|
+
export type DecisionQuestion = {
|
|
97
|
+
type: 'noul';
|
|
98
|
+
instructions?: DecisionText;
|
|
99
|
+
criteria?: {
|
|
100
|
+
true?: DecisionText;
|
|
101
|
+
false?: DecisionText;
|
|
102
|
+
};
|
|
103
|
+
} |
|
|
104
|
+
/** 1 to 255 options; a null description means the option name speaks for itself. */
|
|
105
|
+
{
|
|
106
|
+
type: 'choice';
|
|
107
|
+
instructions?: DecisionText;
|
|
108
|
+
criteria: Record<string, DecisionText>;
|
|
109
|
+
} |
|
|
110
|
+
/** 1 to 10 ordered levels; the score is the probability-weighted mean of the level indexes. */
|
|
111
|
+
{
|
|
112
|
+
type: 'score';
|
|
113
|
+
instructions?: DecisionText;
|
|
114
|
+
criteria: DecisionText[];
|
|
115
|
+
} | {
|
|
116
|
+
type: 'box';
|
|
117
|
+
instructions?: DecisionText;
|
|
118
|
+
};
|
|
119
|
+
/** Instructions and criteria may be text or any JSON value; null means none. */
|
|
120
|
+
export type DecisionText = string | Record<string, unknown> | unknown[] | null;
|
|
121
|
+
/** An image the state refers to by its placeholder name. */
|
|
122
|
+
export interface DecisionAsset {
|
|
123
|
+
type: 'image';
|
|
124
|
+
format: 'png' | 'jpg' | 'jpeg';
|
|
125
|
+
/** Base64 data without a `data:` prefix. */
|
|
126
|
+
data: string;
|
|
127
|
+
}
|
|
128
|
+
export interface DecisionRequest {
|
|
129
|
+
model: string;
|
|
130
|
+
/** What to decide about: text, or any JSON value. */
|
|
131
|
+
state: string | Record<string, unknown> | unknown[];
|
|
132
|
+
questions: Record<string, DecisionQuestion>;
|
|
133
|
+
assets?: Record<string, DecisionAsset>;
|
|
134
|
+
signal?: AbortSignal;
|
|
135
|
+
}
|
|
136
|
+
export type DecisionAnswer = {
|
|
137
|
+
type: 'noul';
|
|
138
|
+
noul: number;
|
|
139
|
+
} | {
|
|
140
|
+
type: 'choice';
|
|
141
|
+
choice: string;
|
|
142
|
+
confidence: number;
|
|
143
|
+
probabilities: Record<string, number>;
|
|
144
|
+
} | {
|
|
145
|
+
type: 'score';
|
|
146
|
+
score: number;
|
|
147
|
+
confidence: number;
|
|
148
|
+
legend: Record<string, string>;
|
|
149
|
+
probabilities: Record<string, number>;
|
|
150
|
+
} |
|
|
151
|
+
/** Edges as fractions from 0 to 1: top and bottom of the image height, left and right of its width. */
|
|
152
|
+
{
|
|
153
|
+
type: 'box';
|
|
154
|
+
box: {
|
|
155
|
+
top: number;
|
|
156
|
+
bottom: number;
|
|
157
|
+
left: number;
|
|
158
|
+
right: number;
|
|
159
|
+
};
|
|
160
|
+
confidence: number | null;
|
|
161
|
+
};
|
|
162
|
+
export interface DecisionResponse {
|
|
163
|
+
/** One answer per question name. */
|
|
164
|
+
answers: Record<string, DecisionAnswer>;
|
|
165
|
+
usage?: Usage;
|
|
166
|
+
}
|
|
167
|
+
export interface ProviderClient {
|
|
168
|
+
chat(request: ChatRequest): Promise<ChatResponse>;
|
|
169
|
+
/** Optional token streaming; callers fall back to `chat` when it is missing. */
|
|
170
|
+
stream?(request: ChatRequest): AsyncIterable<StreamEvent>;
|
|
171
|
+
/** Optional text embeddings, for knowledge bases; providers without them (such as Anthropic) leave it out. */
|
|
172
|
+
embed?(request: EmbedRequest): Promise<EmbedResponse>;
|
|
173
|
+
listModels?(signal?: AbortSignal): Promise<string[]>;
|
|
174
|
+
/** Optional structured decisions, for decision models that do not chat. */
|
|
175
|
+
decide?(request: DecisionRequest): Promise<DecisionResponse>;
|
|
176
|
+
}
|
|
177
|
+
/** What a provider type can do; `chat` covers `chat`, `stream` and `embed`. */
|
|
178
|
+
export type ProviderCapability = 'chat' | 'decision';
|
|
179
|
+
export interface ProviderPreset {
|
|
180
|
+
id: string;
|
|
181
|
+
label: LocalizedText;
|
|
182
|
+
config: Record<string, unknown>;
|
|
183
|
+
/** Suggested models; the dashboard can also fetch the live list. */
|
|
184
|
+
models?: string[];
|
|
185
|
+
}
|
|
186
|
+
export interface ProviderType<C = any> {
|
|
187
|
+
id: string;
|
|
188
|
+
displayName: LocalizedText;
|
|
189
|
+
config: z.ZodType<C>;
|
|
190
|
+
/** Config keys that hold secrets; they are masked in every API response. */
|
|
191
|
+
secrets?: string[];
|
|
192
|
+
presets?: ProviderPreset[];
|
|
193
|
+
/** Defaults to `['chat']`. A type without `chat` is never picked for chat requests. */
|
|
194
|
+
capabilities?: ProviderCapability[];
|
|
195
|
+
create(config: C): ProviderClient;
|
|
196
|
+
}
|
|
197
|
+
export interface ProviderTest {
|
|
198
|
+
ok: boolean;
|
|
199
|
+
at: number;
|
|
200
|
+
latencyMs?: number;
|
|
201
|
+
model?: string;
|
|
202
|
+
error?: string;
|
|
203
|
+
}
|
|
204
|
+
export interface ProviderRecord {
|
|
205
|
+
id: string;
|
|
206
|
+
name: string;
|
|
207
|
+
type: string;
|
|
208
|
+
config: Record<string, any>;
|
|
209
|
+
models: string[];
|
|
210
|
+
defaultModel?: string;
|
|
211
|
+
isDefault?: boolean;
|
|
212
|
+
lastTest?: ProviderTest;
|
|
213
|
+
createdAt: number;
|
|
214
|
+
}
|
|
215
|
+
/** One model of a profile: a model name on a saved provider. */
|
|
216
|
+
export interface ProfileModel {
|
|
217
|
+
provider: string;
|
|
218
|
+
model: string;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* A named choice of model with fallbacks. The first model answers; when a request to it fails,
|
|
222
|
+
* the next one is tried, and so on. A profile id works wherever a provider id is taken.
|
|
223
|
+
*/
|
|
224
|
+
export interface ModelProfile {
|
|
225
|
+
id: string;
|
|
226
|
+
name: string;
|
|
227
|
+
models: ProfileModel[];
|
|
228
|
+
createdAt: number;
|
|
229
|
+
}
|
|
230
|
+
export interface LlmChatOptions extends Omit<ChatRequest, 'model'> {
|
|
231
|
+
/** Provider record id or profile id; the default provider when omitted. */
|
|
232
|
+
provider?: string;
|
|
233
|
+
/** Model name; the provider's default model when omitted. Ignored for a profile, which names its own models. */
|
|
234
|
+
model?: string;
|
|
235
|
+
}
|
|
236
|
+
export interface LlmEmbedOptions {
|
|
237
|
+
/** Provider record id; the default provider when omitted. */
|
|
238
|
+
provider?: string;
|
|
239
|
+
/** Embedding model name; required, since a provider's default model is a chat model. */
|
|
240
|
+
model: string;
|
|
241
|
+
texts: string[];
|
|
242
|
+
signal?: AbortSignal;
|
|
243
|
+
}
|
|
244
|
+
export interface LlmEmbedResult {
|
|
245
|
+
vectors: number[][];
|
|
246
|
+
provider: string;
|
|
247
|
+
model: string;
|
|
248
|
+
/** Length of each vector. */
|
|
249
|
+
dimension: number;
|
|
250
|
+
tokens?: number;
|
|
251
|
+
}
|
|
252
|
+
export interface LlmDecideOptions extends Omit<DecisionRequest, 'model'> {
|
|
253
|
+
/** Provider record id; the default decision provider when omitted. */
|
|
254
|
+
provider?: string;
|
|
255
|
+
/** Model name; the provider's default model when omitted. */
|
|
256
|
+
model?: string;
|
|
257
|
+
}
|
|
258
|
+
export interface LlmDecideResult extends DecisionResponse {
|
|
259
|
+
provider: string;
|
|
260
|
+
providerName: string;
|
|
261
|
+
model: string;
|
|
262
|
+
latencyMs: number;
|
|
263
|
+
}
|
|
264
|
+
/** A chat result that remembers where it came from, so a bot reply can show its source. */
|
|
265
|
+
export interface LlmResult extends ChatResponse {
|
|
266
|
+
provider: string;
|
|
267
|
+
providerName: string;
|
|
268
|
+
model: string;
|
|
269
|
+
latencyMs: number;
|
|
270
|
+
source: Extract<Provenance, {
|
|
271
|
+
kind: 'llm';
|
|
272
|
+
}>;
|
|
273
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushinjs/plugin-providers",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0-next.20261113103537",
|
|
4
|
+
"description": "LLM provider management and the llm service",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/mushinjs/mushin.git",
|
|
10
|
+
"directory": "packages/plugin-providers"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mushin-plugin"
|
|
27
|
+
],
|
|
28
|
+
"mushin": {
|
|
29
|
+
"kind": "app",
|
|
30
|
+
"essential": true,
|
|
31
|
+
"displayName": {
|
|
32
|
+
"en": "Providers",
|
|
33
|
+
"zh-CN": "模型提供商"
|
|
34
|
+
},
|
|
35
|
+
"description": {
|
|
36
|
+
"en": "Manages LLM providers and exposes ctx.llm",
|
|
37
|
+
"zh-CN": "管理大模型提供商并提供 ctx.llm 服务"
|
|
38
|
+
},
|
|
39
|
+
"provides": [
|
|
40
|
+
"llm"
|
|
41
|
+
],
|
|
42
|
+
"requires": {
|
|
43
|
+
"plugins": {
|
|
44
|
+
"@mushinjs/plugin-storage": "*"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@mushinjs/core": "0.2.0-next.20261113103537",
|
|
50
|
+
"@mushinjs/plugin-storage": "0.2.0-next.20261113103537"
|
|
51
|
+
}
|
|
6
52
|
}
|
package/README.md
DELETED