@securecoms/agent-runtime 0.5.0 → 0.6.0

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.
Files changed (2) hide show
  1. package/dist/bin.js +63 -23
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -440,10 +440,16 @@ var LLMDispatcher = class {
440
440
  endpoint;
441
441
  model;
442
442
  apiKey;
443
+ chatPath;
444
+ chatAuth;
445
+ authHeaderName;
443
446
  constructor(endpoint, model, opts = {}) {
444
447
  this.endpoint = normalizeEndpoint(endpoint);
445
448
  this.model = model;
446
449
  this.apiKey = opts.apiKey;
450
+ this.chatPath = opts.chatPath ?? "/v1/chat/completions";
451
+ this.chatAuth = opts.chatAuth ?? "bearer";
452
+ this.authHeaderName = opts.authHeaderName ?? "x-api-key";
447
453
  }
448
454
  async complete(messages, tools) {
449
455
  const body = {
@@ -454,10 +460,12 @@ var LLMDispatcher = class {
454
460
  if (tools && tools.length > 0) {
455
461
  body.tools = tools;
456
462
  }
457
- const url = `${this.endpoint}/v1/chat/completions`;
463
+ const url = `${this.endpoint}${this.chatPath}`;
458
464
  const headers = { "Content-Type": "application/json" };
459
- if (this.apiKey) {
465
+ if (this.apiKey && this.chatAuth === "bearer") {
460
466
  headers.Authorization = `Bearer ${this.apiKey}`;
467
+ } else if (this.apiKey && this.chatAuth === "x-api-key") {
468
+ headers[this.authHeaderName] = this.apiKey;
461
469
  }
462
470
  const resp = await fetch(url, {
463
471
  method: "POST",
@@ -486,10 +494,13 @@ import { join as join2 } from "node:path";
486
494
 
487
495
  // src/llm-providers.json
488
496
  var llm_providers_default = {
489
- openrouter: { chat_base: "https://openrouter.ai/api/v1", models_url: "https://openrouter.ai/api/v1/models", models_id_path: "data[].id", validate_url: "https://openrouter.ai/api/v1/key", needs_key: true },
490
- anthropic: { chat_base: "https://openrouter.ai/api/v1", models_url: "https://openrouter.ai/api/v1/models", models_id_path: "data[].id", validate_url: "https://openrouter.ai/api/v1/key", needs_key: true },
491
- openai: { chat_base: "https://api.openai.com/v1", models_url: "https://api.openai.com/v1/models", models_id_path: "data[].id", validate_url: "https://api.openai.com/v1/models", needs_key: true },
492
- local: { chat_base: "http://127.0.0.1:11434", models_url: "http://127.0.0.1:11434/api/tags", models_id_path: "models[].name", validate_url: null, needs_key: false }
497
+ openrouter: { chat_base: "https://openrouter.ai/api/v1", chat_path: "/v1/chat/completions", chat_auth: "bearer", models_url: "https://openrouter.ai/api/v1/models", models_id_path: "data[].id", validate_url: "https://openrouter.ai/api/v1/key", probe_auth: "bearer", extra_headers: null, catalog_prefix: "self", needs_key: true },
498
+ openai: { chat_base: "https://api.openai.com/v1", chat_path: "/v1/chat/completions", chat_auth: "bearer", models_url: "https://api.openai.com/v1/models", models_id_path: "data[].id", validate_url: "https://api.openai.com/v1/models", probe_auth: "bearer", extra_headers: null, catalog_prefix: "openai/", needs_key: true },
499
+ anthropic: { chat_base: "https://api.anthropic.com/v1", chat_path: "/v1/chat/completions", chat_auth: "bearer", models_url: "https://api.anthropic.com/v1/models", models_id_path: "data[].id", validate_url: "https://api.anthropic.com/v1/models", probe_auth: "x-api-key", extra_headers: { "anthropic-version": "2023-06-01" }, catalog_prefix: "anthropic/", needs_key: true },
500
+ gemini: { chat_base: "https://generativelanguage.googleapis.com/v1beta/openai", chat_path: "/chat/completions", chat_auth: "bearer", models_url: "https://generativelanguage.googleapis.com/v1beta/openai/models", models_id_path: "data[].id", validate_url: "https://generativelanguage.googleapis.com/v1beta/openai/models", probe_auth: "bearer", extra_headers: null, catalog_prefix: "google/", needs_key: true },
501
+ meta: { chat_base: "https://api.meta.ai/v1", chat_path: "/v1/chat/completions", chat_auth: "bearer", models_url: "https://api.meta.ai/v1/models", models_id_path: "data[].id", validate_url: "https://api.meta.ai/v1/models", probe_auth: "bearer", extra_headers: null, catalog_prefix: null, needs_key: true },
502
+ xai: { chat_base: "https://api.x.ai/v1", chat_path: "/v1/chat/completions", chat_auth: "bearer", models_url: "https://api.x.ai/v1/models", models_id_path: "data[].id", validate_url: "https://api.x.ai/v1/models", probe_auth: "bearer", extra_headers: null, catalog_prefix: "x-ai/", needs_key: true },
503
+ local: { chat_base: "http://127.0.0.1:11434", chat_path: "/v1/chat/completions", chat_auth: "none", models_url: "http://127.0.0.1:11434/api/tags", models_id_path: "models[].name", validate_url: null, probe_auth: "none", extra_headers: null, catalog_prefix: null, needs_key: false }
493
504
  };
494
505
 
495
506
  // src/llm-key.ts
@@ -513,7 +524,12 @@ function resolveLlmApiKey(cfg, readFile) {
513
524
 
514
525
  // src/llm-source.ts
515
526
  var PROVIDER_TABLE = Object.freeze(Object.fromEntries(
516
- Object.entries(llm_providers_default).map(([p, e]) => [p, Object.freeze({ endpoint: e.chat_base, needsKey: e.needs_key })])
527
+ Object.entries(llm_providers_default).map(([p, e]) => [p, Object.freeze({
528
+ endpoint: e.chat_base,
529
+ needsKey: e.needs_key,
530
+ chatPath: e.chat_path,
531
+ chatAuth: e.chat_auth
532
+ })])
517
533
  ));
518
534
  var LlmSourceUnavailableError = class extends Error {
519
535
  };
@@ -523,6 +539,9 @@ function providerOrigin(provider) {
523
539
  const u = new URL(entry.endpoint);
524
540
  return `${u.protocol}//${u.host}`.toLowerCase();
525
541
  }
542
+ function providerSlug(provider) {
543
+ return providerOrigin(provider).replace("://", "_").replace(/:/g, "-");
544
+ }
526
545
  var explicitEndpointSet = (env) => Boolean(env.SECURECOMS_LLM_ENDPOINT || env.SECURECOMS_LLM_API_KEY_FILE || env.SECURECOMS_LLM_API_KEY);
527
546
  function isExplicitSource(cfg, env) {
528
547
  return explicitEndpointSet(env) || Boolean(cfg.llmApiKeyFile) || Boolean(cfg.llmApiKey);
@@ -539,7 +558,9 @@ function resolveLlmSource(cfg, explicit, deps) {
539
558
  model: cfg.llmModel,
540
559
  apiKey: resolveLlmApiKey(cfg, deps.readFile),
541
560
  provider: "env",
542
- origin: "env"
561
+ origin: "env",
562
+ chatPath: "/v1/chat/completions",
563
+ chatAuth: "bearer"
543
564
  };
544
565
  }
545
566
  const managedDir = join2(deps.home, ".securecoms", "runtime-config", cfg.identityId);
@@ -567,6 +588,11 @@ function resolveLlmSource(cfg, explicit, deps) {
567
588
  if (typeof provider !== "string" || typeof model !== "string") {
568
589
  throw new LlmSourceUnavailableError(`${sourcePath} is missing "provider" or "model"`);
569
590
  }
591
+ if (!Object.hasOwn(llm_providers_default, provider)) {
592
+ throw new LlmSourceUnavailableError(
593
+ `unknown LLM provider ${JSON.stringify(provider)} in ${sourcePath}`
594
+ );
595
+ }
570
596
  const entry = PROVIDER_TABLE[provider];
571
597
  if (!entry) {
572
598
  throw new LlmSourceUnavailableError(
@@ -575,53 +601,63 @@ function resolveLlmSource(cfg, explicit, deps) {
575
601
  }
576
602
  let apiKey;
577
603
  if (entry.needsKey) {
578
- const keyPath = join2(managedDir, "llm.key");
579
- const hostPath = join2(managedDir, "llm.key.host");
580
- if (!deps.exists(keyPath)) {
604
+ const expectedOrigin = providerOrigin(provider);
605
+ const slug = providerSlug(provider);
606
+ const keyPath = join2(managedDir, `llm.key.${slug}`);
607
+ const hostPath = join2(managedDir, `llm.key.${slug}.host`);
608
+ const legacyKey = join2(managedDir, "llm.key");
609
+ const legacyHost = join2(managedDir, "llm.key.host");
610
+ const useLegacy = !deps.exists(keyPath) && deps.exists(legacyKey) && deps.exists(legacyHost);
611
+ const boundKey = useLegacy ? legacyKey : keyPath;
612
+ const boundHost = useLegacy ? legacyHost : hostPath;
613
+ if (!deps.exists(boundKey)) {
581
614
  throw new LlmSourceUnavailableError(
582
615
  `${keyPath} does not exist \u2014 no key has been written for this managed source, refusing to read it`
583
616
  );
584
617
  }
585
618
  let mode;
586
619
  try {
587
- ({ mode } = deps.statFile(keyPath));
620
+ ({ mode } = deps.statFile(boundKey));
588
621
  } catch (e) {
589
622
  throw new LlmSourceUnavailableError(
590
- `cannot stat ${keyPath}: ${e instanceof Error ? e.message : String(e)}`
623
+ `cannot stat ${boundKey}: ${e instanceof Error ? e.message : String(e)}`
591
624
  );
592
625
  }
593
626
  if ((mode & 63) !== 0) {
594
627
  throw new LlmSourceUnavailableError(
595
- `${keyPath} is readable by other users on this machine \u2014 tighten its permissions to owner-only`
628
+ `${boundKey} is readable by other users on this machine \u2014 tighten its permissions to owner-only`
596
629
  );
597
630
  }
598
- const expectedOrigin = providerOrigin(provider);
599
- if (!deps.exists(hostPath)) {
631
+ if (!deps.exists(boundHost)) {
600
632
  throw new LlmSourceUnavailableError(
601
- `${keyPath} has no ${hostPath} sidecar recording which origin it belongs to \u2014 refusing to send it to ${expectedOrigin}`
633
+ `${boundKey} has no ${boundHost} sidecar recording which origin it belongs to \u2014 refusing to send it to ${expectedOrigin}`
602
634
  );
603
635
  }
604
- const actualOrigin = deps.readFile(hostPath).trim();
636
+ const actualOrigin = deps.readFile(boundHost).trim();
605
637
  if (actualOrigin !== expectedOrigin) {
606
638
  throw new LlmSourceUnavailableError(
607
- `${hostPath} is bound to ${actualOrigin}, but provider ${JSON.stringify(provider)} requires ${expectedOrigin} \u2014 refusing to send the key across origins`
639
+ `${boundHost} is bound to ${actualOrigin}, but provider ${JSON.stringify(provider)} requires ${expectedOrigin} \u2014 refusing to send the key across origins`
608
640
  );
609
641
  }
610
- apiKey = resolveLlmApiKey({ llmApiKeyFile: keyPath }, deps.readFile);
642
+ apiKey = resolveLlmApiKey({ llmApiKeyFile: boundKey }, deps.readFile);
611
643
  }
612
644
  return {
613
645
  endpoint: entry.endpoint,
614
646
  model,
615
647
  ...apiKey !== void 0 ? { apiKey } : {},
616
648
  provider,
617
- origin: "managed"
649
+ origin: "managed",
650
+ chatPath: entry.chatPath,
651
+ chatAuth: entry.chatAuth
618
652
  };
619
653
  }
620
654
  return {
621
655
  endpoint: cfg.llmEndpoint,
622
656
  model: cfg.llmModel,
623
657
  provider: "default",
624
- origin: "default"
658
+ origin: "default",
659
+ chatPath: "/v1/chat/completions",
660
+ chatAuth: "bearer"
625
661
  };
626
662
  }
627
663
 
@@ -637,7 +673,11 @@ function buildLlmShim(cfg, explicit, deps) {
637
673
  return {
638
674
  async complete(messages, tools) {
639
675
  const src = resolveLlmSource(cfg, explicit, deps);
640
- const dispatcher = new LLMDispatcher(src.endpoint, src.model, { apiKey: src.apiKey });
676
+ const dispatcher = new LLMDispatcher(src.endpoint, src.model, {
677
+ apiKey: src.apiKey,
678
+ chatPath: src.chatPath,
679
+ chatAuth: src.chatAuth
680
+ });
641
681
  return dispatcher.complete(messages, tools);
642
682
  }
643
683
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@securecoms/agent-runtime",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "securecoms-agent": "dist/bin.js"