@elisym/cli 0.11.0 → 0.11.1
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/dist/index.js +322 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -338,6 +338,290 @@ var init_anthropic = __esm({
|
|
|
338
338
|
}
|
|
339
339
|
});
|
|
340
340
|
|
|
341
|
+
// src/llm/providers/openai-compatible.ts
|
|
342
|
+
function createOpenAICompatibleProvider(config) {
|
|
343
|
+
const billingMarkers = [...DEFAULT_BILLING_MARKERS, ...config.extraBillingMarkers ?? []];
|
|
344
|
+
function bodyLooksLikeBilling3(body) {
|
|
345
|
+
const lower = body.toLowerCase();
|
|
346
|
+
return billingMarkers.some((marker) => lower.includes(marker));
|
|
347
|
+
}
|
|
348
|
+
async function fetchModels4(apiKey, signal) {
|
|
349
|
+
try {
|
|
350
|
+
const response = await fetchWithTimeout(
|
|
351
|
+
`${config.baseUrl}/models`,
|
|
352
|
+
{ method: "GET", headers: { Authorization: `Bearer ${apiKey}` } },
|
|
353
|
+
signal
|
|
354
|
+
);
|
|
355
|
+
if (!response.ok) {
|
|
356
|
+
return config.fallbackModels;
|
|
357
|
+
}
|
|
358
|
+
const data = await response.json();
|
|
359
|
+
const transformed = [];
|
|
360
|
+
for (const entry of data.data ?? []) {
|
|
361
|
+
const mapped = config.mapModelId ? config.mapModelId(entry.id) : entry.id;
|
|
362
|
+
if (mapped) {
|
|
363
|
+
transformed.push(mapped);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
transformed.sort();
|
|
367
|
+
return transformed.length > 0 ? transformed : config.fallbackModels;
|
|
368
|
+
} catch {
|
|
369
|
+
return config.fallbackModels;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
async function verifyKey3(apiKey, signal) {
|
|
373
|
+
try {
|
|
374
|
+
const response = await fetchWithTimeout(
|
|
375
|
+
`${config.baseUrl}/models`,
|
|
376
|
+
{ method: "GET", headers: { Authorization: `Bearer ${apiKey}` } },
|
|
377
|
+
signal
|
|
378
|
+
);
|
|
379
|
+
if (response.ok) {
|
|
380
|
+
await response.body?.cancel().catch(() => void 0);
|
|
381
|
+
return { ok: true };
|
|
382
|
+
}
|
|
383
|
+
const body = (await response.text().catch(() => "")).slice(0, 500);
|
|
384
|
+
if (response.status === 401 || response.status === 403) {
|
|
385
|
+
return { ok: false, reason: "invalid", status: response.status, body };
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
ok: false,
|
|
389
|
+
reason: "unavailable",
|
|
390
|
+
error: `HTTP ${response.status}: ${body.slice(0, 200)}`
|
|
391
|
+
};
|
|
392
|
+
} catch (error) {
|
|
393
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
394
|
+
return { ok: false, reason: "unavailable", error: message };
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
async function verifyKeyDeep3(apiKey, model, signal) {
|
|
398
|
+
try {
|
|
399
|
+
const response = await fetchWithTimeout(
|
|
400
|
+
`${config.baseUrl}/chat/completions`,
|
|
401
|
+
{
|
|
402
|
+
method: "POST",
|
|
403
|
+
headers: {
|
|
404
|
+
"Content-Type": "application/json",
|
|
405
|
+
Authorization: `Bearer ${apiKey}`
|
|
406
|
+
},
|
|
407
|
+
body: JSON.stringify({
|
|
408
|
+
model,
|
|
409
|
+
max_tokens: 1,
|
|
410
|
+
messages: [{ role: "user", content: "." }]
|
|
411
|
+
})
|
|
412
|
+
},
|
|
413
|
+
signal
|
|
414
|
+
);
|
|
415
|
+
if (response.ok) {
|
|
416
|
+
await response.body?.cancel().catch(() => void 0);
|
|
417
|
+
return { ok: true };
|
|
418
|
+
}
|
|
419
|
+
const body = (await response.text().catch(() => "")).slice(0, 500);
|
|
420
|
+
if (response.status === 401 || response.status === 403) {
|
|
421
|
+
return { ok: false, reason: "invalid", status: response.status, body };
|
|
422
|
+
}
|
|
423
|
+
if (response.status === 402) {
|
|
424
|
+
return { ok: false, reason: "billing", status: response.status, body };
|
|
425
|
+
}
|
|
426
|
+
if ((response.status === 400 || response.status === 429) && bodyLooksLikeBilling3(body)) {
|
|
427
|
+
return { ok: false, reason: "billing", status: response.status, body };
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
ok: false,
|
|
431
|
+
reason: "unavailable",
|
|
432
|
+
error: `HTTP ${response.status}: ${body.slice(0, 200)}`
|
|
433
|
+
};
|
|
434
|
+
} catch (error) {
|
|
435
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
436
|
+
return { ok: false, reason: "unavailable", error: message };
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function createClient(clientConfig) {
|
|
440
|
+
return new OpenAICompatibleClient({
|
|
441
|
+
apiKey: clientConfig.apiKey,
|
|
442
|
+
baseUrl: config.baseUrl,
|
|
443
|
+
model: clientConfig.model ?? config.defaultModel,
|
|
444
|
+
maxTokens: clientConfig.maxTokens ?? DEFAULT_MAX_TOKENS2,
|
|
445
|
+
providerLabel: config.displayName,
|
|
446
|
+
logUsage: clientConfig.logUsage
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
return {
|
|
450
|
+
id: config.id,
|
|
451
|
+
displayName: config.displayName,
|
|
452
|
+
envVar: config.envVar,
|
|
453
|
+
defaultModel: config.defaultModel,
|
|
454
|
+
fallbackModels: config.fallbackModels,
|
|
455
|
+
fetchModels: fetchModels4,
|
|
456
|
+
verifyKey: verifyKey3,
|
|
457
|
+
verifyKeyDeep: verifyKeyDeep3,
|
|
458
|
+
createClient
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
var DEFAULT_MAX_TOKENS2, DEFAULT_BILLING_MARKERS, OpenAICompatibleClient;
|
|
462
|
+
var init_openai_compatible = __esm({
|
|
463
|
+
"src/llm/providers/openai-compatible.ts"() {
|
|
464
|
+
init_http();
|
|
465
|
+
DEFAULT_MAX_TOKENS2 = 4096;
|
|
466
|
+
DEFAULT_BILLING_MARKERS = ["credit balance", "billing", "insufficient_quota", "insufficient"];
|
|
467
|
+
OpenAICompatibleClient = class {
|
|
468
|
+
constructor(config) {
|
|
469
|
+
this.config = config;
|
|
470
|
+
}
|
|
471
|
+
totalIn = 0;
|
|
472
|
+
totalOut = 0;
|
|
473
|
+
logTokens(usage) {
|
|
474
|
+
if (!this.config.logUsage || !usage) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const inputTokens = usage.prompt_tokens ?? 0;
|
|
478
|
+
const outputTokens = usage.completion_tokens ?? 0;
|
|
479
|
+
this.totalIn += inputTokens;
|
|
480
|
+
this.totalOut += outputTokens;
|
|
481
|
+
console.log(
|
|
482
|
+
` [LLM] ${this.config.model} tokens: in=${inputTokens} out=${outputTokens} (total: in=${this.totalIn} out=${this.totalOut})`
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
async complete(systemPrompt, userInput, signal) {
|
|
486
|
+
const response = await fetchWithRetry(
|
|
487
|
+
`${this.config.baseUrl}/chat/completions`,
|
|
488
|
+
{
|
|
489
|
+
method: "POST",
|
|
490
|
+
headers: {
|
|
491
|
+
"Content-Type": "application/json",
|
|
492
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
493
|
+
},
|
|
494
|
+
body: JSON.stringify({
|
|
495
|
+
model: this.config.model,
|
|
496
|
+
max_tokens: this.config.maxTokens,
|
|
497
|
+
messages: [
|
|
498
|
+
{ role: "system", content: systemPrompt },
|
|
499
|
+
{ role: "user", content: userInput }
|
|
500
|
+
]
|
|
501
|
+
})
|
|
502
|
+
},
|
|
503
|
+
signal
|
|
504
|
+
);
|
|
505
|
+
if (!response.ok) {
|
|
506
|
+
throw new Error(
|
|
507
|
+
`${this.config.providerLabel} API error: ${response.status} ${await response.text()}`
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
const data = await response.json();
|
|
511
|
+
this.logTokens(data.usage);
|
|
512
|
+
return data.choices?.[0]?.message?.content ?? "";
|
|
513
|
+
}
|
|
514
|
+
async completeWithTools(systemPrompt, messages, tools, signal) {
|
|
515
|
+
const openaiTools = tools.map((tool) => ({
|
|
516
|
+
type: "function",
|
|
517
|
+
function: {
|
|
518
|
+
name: tool.name,
|
|
519
|
+
description: tool.description,
|
|
520
|
+
parameters: {
|
|
521
|
+
type: "object",
|
|
522
|
+
properties: Object.fromEntries(
|
|
523
|
+
tool.parameters.map((param) => [
|
|
524
|
+
param.name,
|
|
525
|
+
{ type: "string", description: param.description }
|
|
526
|
+
])
|
|
527
|
+
),
|
|
528
|
+
required: tool.parameters.filter((param) => param.required).map((param) => param.name)
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}));
|
|
532
|
+
const response = await fetchWithRetry(
|
|
533
|
+
`${this.config.baseUrl}/chat/completions`,
|
|
534
|
+
{
|
|
535
|
+
method: "POST",
|
|
536
|
+
headers: {
|
|
537
|
+
"Content-Type": "application/json",
|
|
538
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
539
|
+
},
|
|
540
|
+
body: JSON.stringify({
|
|
541
|
+
model: this.config.model,
|
|
542
|
+
max_tokens: this.config.maxTokens,
|
|
543
|
+
messages: [{ role: "system", content: systemPrompt }, ...messages],
|
|
544
|
+
tools: openaiTools
|
|
545
|
+
})
|
|
546
|
+
},
|
|
547
|
+
signal
|
|
548
|
+
);
|
|
549
|
+
if (!response.ok) {
|
|
550
|
+
throw new Error(
|
|
551
|
+
`${this.config.providerLabel} API error: ${response.status} ${await response.text()}`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
const data = await response.json();
|
|
555
|
+
this.logTokens(data.usage);
|
|
556
|
+
const message = data.choices?.[0]?.message;
|
|
557
|
+
const toolCalls = message?.tool_calls ?? [];
|
|
558
|
+
if (toolCalls.length > 0) {
|
|
559
|
+
const calls = toolCalls.map((call) => {
|
|
560
|
+
let args;
|
|
561
|
+
try {
|
|
562
|
+
args = JSON.parse(call.function?.arguments ?? "{}");
|
|
563
|
+
} catch {
|
|
564
|
+
args = {};
|
|
565
|
+
}
|
|
566
|
+
return { id: call.id ?? "", name: call.function?.name ?? "", arguments: args };
|
|
567
|
+
});
|
|
568
|
+
return { type: "tool_use", calls, assistantMessage: message };
|
|
569
|
+
}
|
|
570
|
+
return { type: "text", text: message?.content ?? "" };
|
|
571
|
+
}
|
|
572
|
+
formatToolResultMessages(results) {
|
|
573
|
+
return results.map((result) => ({
|
|
574
|
+
role: "tool",
|
|
575
|
+
tool_call_id: result.callId,
|
|
576
|
+
content: result.content
|
|
577
|
+
}));
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// src/llm/providers/deepseek.ts
|
|
584
|
+
var DEFAULT_MODEL2, FALLBACK_MODELS2, DEEPSEEK_PROVIDER;
|
|
585
|
+
var init_deepseek = __esm({
|
|
586
|
+
"src/llm/providers/deepseek.ts"() {
|
|
587
|
+
init_openai_compatible();
|
|
588
|
+
DEFAULT_MODEL2 = "deepseek-chat";
|
|
589
|
+
FALLBACK_MODELS2 = ["deepseek-chat", "deepseek-reasoner"];
|
|
590
|
+
DEEPSEEK_PROVIDER = createOpenAICompatibleProvider({
|
|
591
|
+
id: "deepseek",
|
|
592
|
+
displayName: "DeepSeek",
|
|
593
|
+
envVar: "DEEPSEEK_API_KEY",
|
|
594
|
+
baseUrl: "https://api.deepseek.com/v1",
|
|
595
|
+
defaultModel: DEFAULT_MODEL2,
|
|
596
|
+
fallbackModels: FALLBACK_MODELS2
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// src/llm/providers/google.ts
|
|
602
|
+
function mapGeminiModelId(id) {
|
|
603
|
+
const stripped = id.startsWith(MODELS_PREFIX) ? id.slice(MODELS_PREFIX.length) : id;
|
|
604
|
+
return stripped.startsWith("gemini") ? stripped : null;
|
|
605
|
+
}
|
|
606
|
+
var DEFAULT_MODEL3, FALLBACK_MODELS3, MODELS_PREFIX, GOOGLE_PROVIDER;
|
|
607
|
+
var init_google = __esm({
|
|
608
|
+
"src/llm/providers/google.ts"() {
|
|
609
|
+
init_openai_compatible();
|
|
610
|
+
DEFAULT_MODEL3 = "gemini-2.5-flash";
|
|
611
|
+
FALLBACK_MODELS3 = ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.0-flash"];
|
|
612
|
+
MODELS_PREFIX = "models/";
|
|
613
|
+
GOOGLE_PROVIDER = createOpenAICompatibleProvider({
|
|
614
|
+
id: "google",
|
|
615
|
+
displayName: "Google (Gemini)",
|
|
616
|
+
envVar: "GEMINI_API_KEY",
|
|
617
|
+
baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
618
|
+
defaultModel: DEFAULT_MODEL3,
|
|
619
|
+
fallbackModels: FALLBACK_MODELS3,
|
|
620
|
+
mapModelId: mapGeminiModelId
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
341
625
|
// src/llm/providers/openai.ts
|
|
342
626
|
function isOpenAIReasoningModel(model) {
|
|
343
627
|
return /^o\d/.test(model) || /^gpt-5(\b|[-.])/.test(model);
|
|
@@ -353,15 +637,15 @@ async function fetchModels2(apiKey, signal) {
|
|
|
353
637
|
signal
|
|
354
638
|
);
|
|
355
639
|
if (!response.ok) {
|
|
356
|
-
return
|
|
640
|
+
return FALLBACK_MODELS4;
|
|
357
641
|
}
|
|
358
642
|
const data = await response.json();
|
|
359
643
|
const models = (data.data ?? []).map((entry) => entry.id).filter(
|
|
360
644
|
(id) => (id.startsWith("gpt-") || id.startsWith("o1") || id.startsWith("o3") || id.startsWith("o4") || id.startsWith("chatgpt-")) && !id.includes("instruct") && !id.includes("realtime") && !id.includes("audio") && !id.includes("tts") && !id.includes("whisper")
|
|
361
645
|
).sort();
|
|
362
|
-
return models.length > 0 ? models :
|
|
646
|
+
return models.length > 0 ? models : FALLBACK_MODELS4;
|
|
363
647
|
} catch {
|
|
364
|
-
return
|
|
648
|
+
return FALLBACK_MODELS4;
|
|
365
649
|
}
|
|
366
650
|
}
|
|
367
651
|
async function verifyKey2(apiKey, signal) {
|
|
@@ -442,13 +726,13 @@ async function verifyKeyDeep2(apiKey, model, signal) {
|
|
|
442
726
|
return { ok: false, reason: "unavailable", error: message };
|
|
443
727
|
}
|
|
444
728
|
}
|
|
445
|
-
var
|
|
729
|
+
var DEFAULT_MODEL4, DEFAULT_MAX_TOKENS3, FALLBACK_MODELS4, OpenAIClient, BILLING_BODY_MARKERS2, OPENAI_PROVIDER;
|
|
446
730
|
var init_openai = __esm({
|
|
447
731
|
"src/llm/providers/openai.ts"() {
|
|
448
732
|
init_http();
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
733
|
+
DEFAULT_MODEL4 = "gpt-4o-mini";
|
|
734
|
+
DEFAULT_MAX_TOKENS3 = 4096;
|
|
735
|
+
FALLBACK_MODELS4 = ["gpt-4o", "gpt-4o-mini", "o3-mini"];
|
|
452
736
|
OpenAIClient = class {
|
|
453
737
|
constructor(config) {
|
|
454
738
|
this.config = config;
|
|
@@ -571,15 +855,15 @@ var init_openai = __esm({
|
|
|
571
855
|
id: "openai",
|
|
572
856
|
displayName: "OpenAI (GPT)",
|
|
573
857
|
envVar: "OPENAI_API_KEY",
|
|
574
|
-
defaultModel:
|
|
575
|
-
fallbackModels:
|
|
858
|
+
defaultModel: DEFAULT_MODEL4,
|
|
859
|
+
fallbackModels: FALLBACK_MODELS4,
|
|
576
860
|
fetchModels: fetchModels2,
|
|
577
861
|
verifyKey: verifyKey2,
|
|
578
862
|
verifyKeyDeep: verifyKeyDeep2,
|
|
579
863
|
createClient: (config) => new OpenAIClient({
|
|
580
864
|
apiKey: config.apiKey,
|
|
581
|
-
model: config.model ??
|
|
582
|
-
maxTokens: config.maxTokens ??
|
|
865
|
+
model: config.model ?? DEFAULT_MODEL4,
|
|
866
|
+
maxTokens: config.maxTokens ?? DEFAULT_MAX_TOKENS3,
|
|
583
867
|
logUsage: config.logUsage
|
|
584
868
|
}),
|
|
585
869
|
isReasoningModel: isOpenAIReasoningModel
|
|
@@ -587,6 +871,24 @@ var init_openai = __esm({
|
|
|
587
871
|
}
|
|
588
872
|
});
|
|
589
873
|
|
|
874
|
+
// src/llm/providers/xai.ts
|
|
875
|
+
var DEFAULT_MODEL5, FALLBACK_MODELS5, XAI_PROVIDER;
|
|
876
|
+
var init_xai = __esm({
|
|
877
|
+
"src/llm/providers/xai.ts"() {
|
|
878
|
+
init_openai_compatible();
|
|
879
|
+
DEFAULT_MODEL5 = "grok-3-mini";
|
|
880
|
+
FALLBACK_MODELS5 = ["grok-4", "grok-3", "grok-3-mini"];
|
|
881
|
+
XAI_PROVIDER = createOpenAICompatibleProvider({
|
|
882
|
+
id: "xai",
|
|
883
|
+
displayName: "xAI (Grok)",
|
|
884
|
+
envVar: "XAI_API_KEY",
|
|
885
|
+
baseUrl: "https://api.x.ai/v1",
|
|
886
|
+
defaultModel: DEFAULT_MODEL5,
|
|
887
|
+
fallbackModels: FALLBACK_MODELS5
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
|
|
590
892
|
// src/llm/registry.ts
|
|
591
893
|
function registerLlmProvider(descriptor) {
|
|
592
894
|
REGISTRY.set(descriptor.id, descriptor);
|
|
@@ -604,10 +906,16 @@ var REGISTRY;
|
|
|
604
906
|
var init_registry = __esm({
|
|
605
907
|
"src/llm/registry.ts"() {
|
|
606
908
|
init_anthropic();
|
|
909
|
+
init_deepseek();
|
|
910
|
+
init_google();
|
|
607
911
|
init_openai();
|
|
912
|
+
init_xai();
|
|
608
913
|
REGISTRY = /* @__PURE__ */ new Map();
|
|
609
914
|
registerLlmProvider(ANTHROPIC_PROVIDER);
|
|
610
915
|
registerLlmProvider(OPENAI_PROVIDER);
|
|
916
|
+
registerLlmProvider(XAI_PROVIDER);
|
|
917
|
+
registerLlmProvider(GOOGLE_PROVIDER);
|
|
918
|
+
registerLlmProvider(DEEPSEEK_PROVIDER);
|
|
611
919
|
}
|
|
612
920
|
});
|
|
613
921
|
|
|
@@ -1510,7 +1818,7 @@ var JobLedger = class {
|
|
|
1510
1818
|
init_llm();
|
|
1511
1819
|
|
|
1512
1820
|
// src/llm/resolve.ts
|
|
1513
|
-
var
|
|
1821
|
+
var DEFAULT_MAX_TOKENS4 = 4096;
|
|
1514
1822
|
function resolveSkillLlm(input, agentDefault) {
|
|
1515
1823
|
const override = input.llmOverride;
|
|
1516
1824
|
const overridePairSet = override?.provider !== void 0 && override.model !== void 0;
|
|
@@ -1528,7 +1836,7 @@ function resolveSkillLlm(input, agentDefault) {
|
|
|
1528
1836
|
error: `Skill "${input.skillName}" at ${input.skillMdPath}: LLM model is required - declare "provider" + "model" in the SKILL.md frontmatter or set agent-level llm via 'npx @elisym/cli profile <agent>'.`
|
|
1529
1837
|
};
|
|
1530
1838
|
}
|
|
1531
|
-
const maxTokens = override?.maxTokens ?? agentDefault?.max_tokens ??
|
|
1839
|
+
const maxTokens = override?.maxTokens ?? agentDefault?.max_tokens ?? DEFAULT_MAX_TOKENS4;
|
|
1532
1840
|
return { provider, model, maxTokens };
|
|
1533
1841
|
}
|
|
1534
1842
|
|
|
@@ -1554,7 +1862,7 @@ function resolveTripleForOverride(override, agentDefault) {
|
|
|
1554
1862
|
if (!provider || !model) {
|
|
1555
1863
|
return void 0;
|
|
1556
1864
|
}
|
|
1557
|
-
const maxTokens = override?.maxTokens ?? agentDefault?.max_tokens ??
|
|
1865
|
+
const maxTokens = override?.maxTokens ?? agentDefault?.max_tokens ?? DEFAULT_MAX_TOKENS4;
|
|
1558
1866
|
return { provider, model, maxTokens };
|
|
1559
1867
|
}
|
|
1560
1868
|
|