loopengine 0.1.6 → 0.1.8

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/README.md CHANGED
@@ -466,6 +466,18 @@ you, using the matching API key from the environment:
466
466
  | `'anthropic'` | `ANTHROPIC_API_KEY` | no — defaults to `claude-sonnet-5` |
467
467
  | `'openai'` | `OPENAI_API_KEY` | yes |
468
468
  | `'deepseek'` | `DEEPSEEK_API_KEY` | yes |
469
+ | `'kimi'` | `MOONSHOT_API_KEY` | yes |
470
+ | `'glm'` | `GLM_API_KEY` | yes |
471
+ | `'gemini'` | `GEMINI_API_KEY` | yes |
472
+
473
+ `'kimi'`'s env var is `MOONSHOT_API_KEY`, not `KIMI_API_KEY` — deliberate,
474
+ matching Moonshot AI's own docs (the API/company behind Kimi) rather than
475
+ this package's own provider name. `openai`/`deepseek`/`kimi`/`glm`/
476
+ `gemini` all reuse the same OpenAI-Chat-Completions-compatible request
477
+ translation, just pointed at each provider's own base URL — see
478
+ `core/model-calls/*.ts` for the provider-specific details (base URLs,
479
+ `max_completion_tokens` vs `max_tokens`, and Gemini's own "still in beta"
480
+ caveat on Google's compatibility layer).
469
481
 
470
482
  For anything else (a custom SDK client, a canned/simulated model for
471
483
  testing), export your own `createModelCall(): ModelCall` instead.
package/dist/bin/cli.d.ts CHANGED
@@ -7,7 +7,7 @@ export declare class AgentNotFoundError extends Error {
7
7
  }
8
8
  export declare class AgentModelError extends Error {
9
9
  }
10
- type Provider = 'anthropic' | 'openai' | 'deepseek';
10
+ type Provider = 'anthropic' | 'openai' | 'deepseek' | 'kimi' | 'glm' | 'gemini';
11
11
  export interface AgentTemplateOptions {
12
12
  systemPrompt?: string;
13
13
  model?: {
package/dist/bin/cli.js CHANGED
@@ -26,6 +26,11 @@ const MODEL_ENV_VAR = {
26
26
  anthropic: 'ANTHROPIC_API_KEY',
27
27
  openai: 'OPENAI_API_KEY',
28
28
  deepseek: 'DEEPSEEK_API_KEY',
29
+ // Moonshot AI's own env var name, not KIMI_API_KEY — see
30
+ // kimi-model-call.ts's own header comment for why.
31
+ kimi: 'MOONSHOT_API_KEY',
32
+ glm: 'GLM_API_KEY',
33
+ gemini: 'GEMINI_API_KEY',
29
34
  };
30
35
  // Escapes a value for safe interpolation into a single-quoted TS string
31
36
  // literal in generated code — an operator-supplied systemPrompt (from
@@ -237,9 +237,10 @@ export type HttpNotifierConfig = {
237
237
  * export your own `createModelCall` for anything this can't express (a
238
238
  * canned/simulated `ModelCall` for a demo, a custom SDK client, a
239
239
  * provider this doesn't list). `model` is required for `openai`/
240
- * `deepseek` (no safe hardcoded default — a flagship model name changes
241
- * too often to bake one in) but optional for `anthropic` (defaults to
242
- * `'claude-sonnet-5'`), mirroring each factory's own options exactly. */
240
+ * `deepseek`/`kimi`/`glm`/`gemini` (no safe hardcoded default — a
241
+ * flagship model name changes too often to bake one in) but optional for
242
+ * `anthropic` (defaults to `'claude-sonnet-5'`), mirroring each factory's
243
+ * own options exactly. */
243
244
  export type AgentModelConfig = {
244
245
  provider: 'anthropic';
245
246
  model?: string;
@@ -256,6 +257,24 @@ export type AgentModelConfig = {
256
257
  apiKey?: string;
257
258
  maxTokens?: number;
258
259
  baseURL?: string;
260
+ } | {
261
+ provider: 'kimi';
262
+ model: string;
263
+ apiKey?: string;
264
+ maxTokens?: number;
265
+ baseURL?: string;
266
+ } | {
267
+ provider: 'glm';
268
+ model: string;
269
+ apiKey?: string;
270
+ maxTokens?: number;
271
+ baseURL?: string;
272
+ } | {
273
+ provider: 'gemini';
274
+ model: string;
275
+ apiKey?: string;
276
+ maxTokens?: number;
277
+ baseURL?: string;
259
278
  };
260
279
  export interface ToolSchema {
261
280
  name: string;
@@ -47,8 +47,20 @@ export async function synthesizeCreateModelCall(model) {
47
47
  const { createOpenAIModelCall } = await import('./model-calls/openai-model-call.js');
48
48
  return () => (cached ??= createOpenAIModelCall(model));
49
49
  }
50
- const { createDeepSeekModelCall } = await import('./model-calls/deepseek-model-call.js');
51
- return () => (cached ??= createDeepSeekModelCall(model));
50
+ if (model.provider === 'deepseek') {
51
+ const { createDeepSeekModelCall } = await import('./model-calls/deepseek-model-call.js');
52
+ return () => (cached ??= createDeepSeekModelCall(model));
53
+ }
54
+ if (model.provider === 'kimi') {
55
+ const { createKimiModelCall } = await import('./model-calls/kimi-model-call.js');
56
+ return () => (cached ??= createKimiModelCall(model));
57
+ }
58
+ if (model.provider === 'glm') {
59
+ const { createGlmModelCall } = await import('./model-calls/glm-model-call.js');
60
+ return () => (cached ??= createGlmModelCall(model));
61
+ }
62
+ const { createGeminiModelCall } = await import('./model-calls/gemini-model-call.js');
63
+ return () => (cached ??= createGeminiModelCall(model));
52
64
  }
53
65
  /** Resolves one directory entry to an agent module's path, or undefined if
54
66
  * this entry isn't one — a `.ts`/`.js` file directly (the common case), or
@@ -0,0 +1,17 @@
1
+ import OpenAI from 'openai';
2
+ import type { ModelCall } from '#core/run-agent.js';
3
+ export interface GeminiModelCallOptions {
4
+ /** Defaults to the GEMINI_API_KEY env var. */
5
+ apiKey?: string;
6
+ /** No hardcoded default — same reasoning as OpenAIModelCallOptions.model:
7
+ * pass the one you actually want. */
8
+ model: string;
9
+ maxTokens?: number;
10
+ /** Defaults to Google's own OpenAI-compatible endpoint — override to
11
+ * point at a proxy/mirror. Trailing slash required (see this file's
12
+ * own header comment). */
13
+ baseURL?: string;
14
+ /** Inject a pre-configured client instead of building one from apiKey/baseURL — e.g. to pass a custom `fetch` in tests. */
15
+ client?: OpenAI;
16
+ }
17
+ export declare function createGeminiModelCall(options: GeminiModelCallOptions): ModelCall;
@@ -0,0 +1,53 @@
1
+ // A sixth real ModelCall implementation. Google's own OpenAI-compatible
2
+ // endpoint for Gemini (ai.google.dev/gemini-api/docs/openai) supports the
3
+ // same messages/tools/tool_choice request shape and tool_calls response
4
+ // shape as OpenAI's Chat Completions API, so this reuses
5
+ // openai-model-call.ts's translation verbatim via the real `openai` SDK
6
+ // client, same pattern deepseek/kimi/glm-model-call.ts already
7
+ // established — rather than a bespoke native @google/genai integration
8
+ // (Gemini's own SDK has a meaningfully different message-role/content-
9
+ // parts/function-calling shape).
10
+ //
11
+ // Caveat, from Google's own docs: "Support for the OpenAI libraries is
12
+ // still in beta while we extend feature support" — this is the one
13
+ // provider here riding a compatibility layer its own vendor calls beta,
14
+ // not a fully stable wire contract the way DeepSeek/Kimi/GLM's own docs
15
+ // present theirs. Uses `max_completion_tokens`, same as Kimi/GLM.
16
+ //
17
+ // The trailing slash on the base URL matters — Google's endpoint 404s
18
+ // without it (confirmed against their own docs' exact example URL).
19
+ import OpenAI from 'openai';
20
+ import { toMessageParams, toModelResponse } from './openai-model-call.js';
21
+ export function createGeminiModelCall(options) {
22
+ if (!options.client && !options.apiKey && !process.env.GEMINI_API_KEY) {
23
+ // Fails here, with a Gemini-specific message, rather than letting the
24
+ // OpenAI SDK's own constructor validation reject it — that error says
25
+ // "set OPENAI_API_KEY", which would send someone who never touched
26
+ // that variable chasing the wrong fix.
27
+ throw new Error('createGeminiModelCall: no apiKey given and GEMINI_API_KEY is not set.');
28
+ }
29
+ const client = options.client ??
30
+ new OpenAI({
31
+ // Explicitly null, not undefined, if neither is set — see
32
+ // kimi-model-call.ts's identical comment for why this matters even
33
+ // though the guard above already rules out reaching here with no
34
+ // key at all.
35
+ apiKey: options.apiKey ?? process.env.GEMINI_API_KEY ?? null,
36
+ baseURL: options.baseURL ?? 'https://generativelanguage.googleapis.com/v1beta/openai/',
37
+ });
38
+ const maxTokens = options.maxTokens ?? 4096;
39
+ return async (messages, system, tools) => {
40
+ const response = await client.chat.completions.create({
41
+ model: options.model,
42
+ max_completion_tokens: maxTokens,
43
+ messages: [{ role: 'system', content: system }, ...messages.flatMap(toMessageParams)],
44
+ tools: tools.length
45
+ ? tools.map((t) => ({
46
+ type: 'function',
47
+ function: { name: t.name, description: t.description, parameters: t.input_schema },
48
+ }))
49
+ : undefined,
50
+ });
51
+ return toModelResponse(response.choices[0]);
52
+ };
53
+ }
@@ -0,0 +1,15 @@
1
+ import OpenAI from 'openai';
2
+ import type { ModelCall } from '#core/run-agent.js';
3
+ export interface GlmModelCallOptions {
4
+ /** Defaults to the GLM_API_KEY env var. */
5
+ apiKey?: string;
6
+ /** No hardcoded default — same reasoning as OpenAIModelCallOptions.model:
7
+ * pass the one you actually want. */
8
+ model: string;
9
+ maxTokens?: number;
10
+ /** Defaults to Zhipu's own API — override to point at a proxy/mirror. */
11
+ baseURL?: string;
12
+ /** Inject a pre-configured client instead of building one from apiKey/baseURL — e.g. to pass a custom `fetch` in tests. */
13
+ client?: OpenAI;
14
+ }
15
+ export declare function createGlmModelCall(options: GlmModelCallOptions): ModelCall;
@@ -0,0 +1,46 @@
1
+ // A fifth real ModelCall implementation. GLM (Zhipu AI / Z.ai)'s Chat
2
+ // Completions API is OpenAI-compatible — confirmed against Zhipu's own
3
+ // docs (docs.bigmodel.cn/cn/guide/develop/openai/introduction): same
4
+ // messages/tools/tool_choice request shape, same tool_calls response
5
+ // shape — so this reuses openai-model-call.ts's message/response
6
+ // translation verbatim via the real `openai` SDK client, just pointed at
7
+ // Zhipu's base URL, rather than duplicating that logic. Same pattern
8
+ // deepseek-model-call.ts/kimi-model-call.ts already established.
9
+ //
10
+ // Uses the newer `max_completion_tokens` field, same as Kimi — no
11
+ // DeepSeek-style documented exception for GLM.
12
+ import OpenAI from 'openai';
13
+ import { toMessageParams, toModelResponse } from './openai-model-call.js';
14
+ export function createGlmModelCall(options) {
15
+ if (!options.client && !options.apiKey && !process.env.GLM_API_KEY) {
16
+ // Fails here, with a GLM-specific message, rather than letting the
17
+ // OpenAI SDK's own constructor validation reject it — that error says
18
+ // "set OPENAI_API_KEY", which would send someone who never touched
19
+ // that variable chasing the wrong fix.
20
+ throw new Error('createGlmModelCall: no apiKey given and GLM_API_KEY is not set.');
21
+ }
22
+ const client = options.client ??
23
+ new OpenAI({
24
+ // Explicitly null, not undefined, if neither is set — see
25
+ // kimi-model-call.ts's identical comment for why this matters even
26
+ // though the guard above already rules out reaching here with no
27
+ // key at all.
28
+ apiKey: options.apiKey ?? process.env.GLM_API_KEY ?? null,
29
+ baseURL: options.baseURL ?? 'https://open.bigmodel.cn/api/paas/v4/',
30
+ });
31
+ const maxTokens = options.maxTokens ?? 4096;
32
+ return async (messages, system, tools) => {
33
+ const response = await client.chat.completions.create({
34
+ model: options.model,
35
+ max_completion_tokens: maxTokens,
36
+ messages: [{ role: 'system', content: system }, ...messages.flatMap(toMessageParams)],
37
+ tools: tools.length
38
+ ? tools.map((t) => ({
39
+ type: 'function',
40
+ function: { name: t.name, description: t.description, parameters: t.input_schema },
41
+ }))
42
+ : undefined,
43
+ });
44
+ return toModelResponse(response.choices[0]);
45
+ };
46
+ }
@@ -0,0 +1,15 @@
1
+ import OpenAI from 'openai';
2
+ import type { ModelCall } from '#core/run-agent.js';
3
+ export interface KimiModelCallOptions {
4
+ /** Defaults to the MOONSHOT_API_KEY env var. */
5
+ apiKey?: string;
6
+ /** No hardcoded default — same reasoning as OpenAIModelCallOptions.model:
7
+ * pass the one you actually want. */
8
+ model: string;
9
+ maxTokens?: number;
10
+ /** Defaults to Moonshot's own API — override to point at a proxy/mirror. */
11
+ baseURL?: string;
12
+ /** Inject a pre-configured client instead of building one from apiKey/baseURL — e.g. to pass a custom `fetch` in tests. */
13
+ client?: OpenAI;
14
+ }
15
+ export declare function createKimiModelCall(options: KimiModelCallOptions): ModelCall;
@@ -0,0 +1,59 @@
1
+ // A fourth real ModelCall implementation. Kimi (Moonshot AI)'s Chat
2
+ // Completions API is wire-compatible with OpenAI's — confirmed against
3
+ // Moonshot's own docs (platform.kimi.ai/docs/api/chat): same messages/
4
+ // tools/tool_choice request shape, same tool_calls response shape — so
5
+ // this reuses openai-model-call.ts's message/response translation
6
+ // verbatim via the real `openai` SDK client, just pointed at Moonshot's
7
+ // base URL, rather than duplicating that logic. Same pattern
8
+ // deepseek-model-call.ts already established for a third provider.
9
+ //
10
+ // Unlike DeepSeek, Moonshot's docs use the newer `max_completion_tokens`
11
+ // field (matching the current OpenAI SDK) rather than the deprecated
12
+ // `max_tokens` DeepSeek's own docs still list — decided at the request-
13
+ // building call site below, since toMessageParams/toModelResponse don't
14
+ // touch that field either way.
15
+ //
16
+ // Env var is MOONSHOT_API_KEY, not KIMI_API_KEY, despite every other
17
+ // provider here matching its own literal exactly — deliberate: this
18
+ // matches the underlying API/company's own docs and what anyone
19
+ // copy-pasting from Moonshot's own examples already has set, not this
20
+ // package's own provider name.
21
+ import OpenAI from 'openai';
22
+ import { toMessageParams, toModelResponse } from './openai-model-call.js';
23
+ export function createKimiModelCall(options) {
24
+ if (!options.client && !options.apiKey && !process.env.MOONSHOT_API_KEY) {
25
+ // Fails here, with a Kimi-specific message, rather than letting the
26
+ // OpenAI SDK's own constructor validation reject it — that error says
27
+ // "set OPENAI_API_KEY", which would send someone who never touched
28
+ // that variable chasing the wrong fix.
29
+ throw new Error('createKimiModelCall: no apiKey given and MOONSHOT_API_KEY is not set.');
30
+ }
31
+ const client = options.client ??
32
+ new OpenAI({
33
+ // Explicitly null, not undefined, if neither is set — the OpenAI
34
+ // SDK's own constructor falls back to reading OPENAI_API_KEY itself
35
+ // when apiKey is undefined (its normal, correct behavior for
36
+ // createOpenAIModelCall), which here would silently authenticate
37
+ // against Moonshot with an OpenAI key if one happens to be set. The
38
+ // guard above already rules out reaching here with no key at all,
39
+ // but null (not undefined) is kept regardless so this line is
40
+ // correct on its own, not just because of the guard before it.
41
+ apiKey: options.apiKey ?? process.env.MOONSHOT_API_KEY ?? null,
42
+ baseURL: options.baseURL ?? 'https://api.moonshot.ai/v1',
43
+ });
44
+ const maxTokens = options.maxTokens ?? 4096;
45
+ return async (messages, system, tools) => {
46
+ const response = await client.chat.completions.create({
47
+ model: options.model,
48
+ max_completion_tokens: maxTokens,
49
+ messages: [{ role: 'system', content: system }, ...messages.flatMap(toMessageParams)],
50
+ tools: tools.length
51
+ ? tools.map((t) => ({
52
+ type: 'function',
53
+ function: { name: t.name, description: t.description, parameters: t.input_schema },
54
+ }))
55
+ : undefined,
56
+ });
57
+ return toModelResponse(response.choices[0]);
58
+ };
59
+ }
package/dist/index.d.ts CHANGED
@@ -14,6 +14,12 @@ export { createOpenAIModelCall } from './core/model-calls/openai-model-call.js';
14
14
  export type { OpenAIModelCallOptions } from './core/model-calls/openai-model-call.js';
15
15
  export { createDeepSeekModelCall } from './core/model-calls/deepseek-model-call.js';
16
16
  export type { DeepSeekModelCallOptions } from './core/model-calls/deepseek-model-call.js';
17
+ export { createKimiModelCall } from './core/model-calls/kimi-model-call.js';
18
+ export type { KimiModelCallOptions } from './core/model-calls/kimi-model-call.js';
19
+ export { createGlmModelCall } from './core/model-calls/glm-model-call.js';
20
+ export type { GlmModelCallOptions } from './core/model-calls/glm-model-call.js';
21
+ export { createGeminiModelCall } from './core/model-calls/gemini-model-call.js';
22
+ export type { GeminiModelCallOptions } from './core/model-calls/gemini-model-call.js';
17
23
  export { discoverAgents, loadAgentModule, synthesizeCreateModelCall } from './core/discover-agents.js';
18
24
  export type { AgentModule } from './core/discover-agents.js';
19
25
  export { agentAsTool } from './core/agent-as-tool.js';
package/dist/index.js CHANGED
@@ -16,6 +16,9 @@ export { VectorIndex, embed, cosineSimilarity } from './core/vector-index.js';
16
16
  export { createAnthropicModelCall } from './core/model-calls/anthropic-model-call.js';
17
17
  export { createOpenAIModelCall } from './core/model-calls/openai-model-call.js';
18
18
  export { createDeepSeekModelCall } from './core/model-calls/deepseek-model-call.js';
19
+ export { createKimiModelCall } from './core/model-calls/kimi-model-call.js';
20
+ export { createGlmModelCall } from './core/model-calls/glm-model-call.js';
21
+ export { createGeminiModelCall } from './core/model-calls/gemini-model-call.js';
19
22
  export { discoverAgents, loadAgentModule, synthesizeCreateModelCall } from './core/discover-agents.js';
20
23
  export { agentAsTool } from './core/agent-as-tool.js';
21
24
  // Everything below backs the Admin UI (agents-config/list/global-config
@@ -2,7 +2,7 @@ export declare class AgentEditNotSupportedError extends Error {
2
2
  }
3
3
  export declare class AgentFileNotFoundError extends Error {
4
4
  }
5
- type Provider = 'anthropic' | 'openai' | 'deepseek';
5
+ type Provider = 'anthropic' | 'openai' | 'deepseek' | 'kimi' | 'glm' | 'gemini';
6
6
  export interface AgentEditableFields {
7
7
  systemPrompt?: string;
8
8
  model?: {
@@ -45,6 +45,11 @@ const MODEL_ENV_VAR = {
45
45
  anthropic: 'ANTHROPIC_API_KEY',
46
46
  openai: 'OPENAI_API_KEY',
47
47
  deepseek: 'DEEPSEEK_API_KEY',
48
+ // Moonshot AI's own env var name, not KIMI_API_KEY — see
49
+ // core/model-calls/kimi-model-call.ts's own header comment for why.
50
+ kimi: 'MOONSHOT_API_KEY',
51
+ glm: 'GLM_API_KEY',
52
+ gemini: 'GEMINI_API_KEY',
48
53
  };
49
54
  // The exact shape agentIndexTemplate (bin/cli.ts) always generates for the
50
55
  // model field's trailing comment — used to recognize a comment this
@@ -653,7 +653,7 @@ export const agentsConfigPageHtml = `<!doctype html>
653
653
  '<form class="add-source" id="modelForm" style="display:none">' +
654
654
  '<label>Provider' +
655
655
  '<select name="provider">' +
656
- ['anthropic', 'openai', 'deepseek'].map(function (p) {
656
+ ['anthropic', 'openai', 'deepseek', 'kimi', 'glm', 'gemini'].map(function (p) {
657
657
  return '<option value="' + p + '"' + (p === cfg.model.provider ? ' selected' : '') + '>' + p + '</option>';
658
658
  }).join('') +
659
659
  '</select>' +
@@ -1,6 +1,6 @@
1
1
  import type { AgentConfig } from '../core/agent-config.js';
2
2
  export interface ModelProviderInfo {
3
- provider: 'anthropic' | 'openai' | 'deepseek';
3
+ provider: 'anthropic' | 'openai' | 'deepseek' | 'kimi' | 'glm' | 'gemini';
4
4
  envVar: string;
5
5
  configured: boolean;
6
6
  }
@@ -8,6 +8,11 @@ const KNOWN_PROVIDERS = [
8
8
  { provider: 'anthropic', envVar: 'ANTHROPIC_API_KEY' },
9
9
  { provider: 'openai', envVar: 'OPENAI_API_KEY' },
10
10
  { provider: 'deepseek', envVar: 'DEEPSEEK_API_KEY' },
11
+ // Moonshot AI's own env var name, not KIMI_API_KEY — see
12
+ // core/model-calls/kimi-model-call.ts's own header comment for why.
13
+ { provider: 'kimi', envVar: 'MOONSHOT_API_KEY' },
14
+ { provider: 'glm', envVar: 'GLM_API_KEY' },
15
+ { provider: 'gemini', envVar: 'GEMINI_API_KEY' },
11
16
  ];
12
17
  /** Every registered agent's resolved model, alongside whether each
13
18
  * supported provider actually has credentials available in this
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopengine",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "description": "Runtime for tool-using AI agents with a transparent ReAct loop.",
6
6
  "license": "MIT",
@@ -41,6 +41,12 @@
41
41
  "dist/core/model-calls/openai-model-call.d.ts",
42
42
  "dist/core/model-calls/deepseek-model-call.js",
43
43
  "dist/core/model-calls/deepseek-model-call.d.ts",
44
+ "dist/core/model-calls/kimi-model-call.js",
45
+ "dist/core/model-calls/kimi-model-call.d.ts",
46
+ "dist/core/model-calls/glm-model-call.js",
47
+ "dist/core/model-calls/glm-model-call.d.ts",
48
+ "dist/core/model-calls/gemini-model-call.js",
49
+ "dist/core/model-calls/gemini-model-call.d.ts",
44
50
  "dist/core/discover-agents.js",
45
51
  "dist/core/discover-agents.d.ts",
46
52
  "dist/core/agent-as-tool.js",