@jambonz/llm 0.6.6 → 0.6.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/dist/index.cjs CHANGED
@@ -34,6 +34,7 @@ __export(src_exports, {
34
34
  createLlm: () => createLlm,
35
35
  getAdapterFactory: () => getAdapterFactory,
36
36
  getManifest: () => getManifest,
37
+ getVendorManifest: () => getVendorManifest,
37
38
  listVendors: () => listVendors,
38
39
  normalizeAuth: () => normalizeAuth,
39
40
  pickAuthKind: () => pickAuthKind,
@@ -45,8 +46,9 @@ module.exports = __toCommonJS(src_exports);
45
46
 
46
47
  // src/registry.ts
47
48
  var registry = /* @__PURE__ */ new Map();
49
+ var credentialVendors = /* @__PURE__ */ new Map();
48
50
  function registerAdapter(factory) {
49
- if (registry.has(factory.vendor)) {
51
+ if (registry.has(factory.vendor) || credentialVendors.has(factory.vendor)) {
50
52
  throw new Error(
51
53
  `Adapter already registered for vendor '${factory.vendor}'. Use replaceAdapter() if you intended to override.`
52
54
  );
@@ -58,6 +60,15 @@ function registerAdapter(factory) {
58
60
  }
59
61
  registry.set(factory.vendor, factory);
60
62
  }
63
+ function registerCredentialVendor(manifest) {
64
+ if (!manifest.credentialOnly) {
65
+ throw new Error(`Vendor '${manifest.vendor}' manifest is not marked credentialOnly.`);
66
+ }
67
+ if (registry.has(manifest.vendor) || credentialVendors.has(manifest.vendor)) {
68
+ throw new Error(`Vendor '${manifest.vendor}' is already registered.`);
69
+ }
70
+ credentialVendors.set(manifest.vendor, manifest);
71
+ }
61
72
  function replaceAdapter(factory) {
62
73
  registry.set(factory.vendor, factory);
63
74
  }
@@ -67,6 +78,11 @@ function unregisterAdapter(vendor) {
67
78
  function getAdapterFactory(vendor) {
68
79
  const factory = registry.get(vendor);
69
80
  if (!factory) {
81
+ if (credentialVendors.has(vendor)) {
82
+ throw new Error(
83
+ `Vendor '${vendor}' is credential-only: @jambonz/llm has no adapter for it.`
84
+ );
85
+ }
70
86
  const known = Array.from(registry.keys()).sort().join(", ") || "(none)";
71
87
  throw new Error(
72
88
  `Unknown vendor '${vendor}'. Registered vendors: ${known}`
@@ -74,11 +90,22 @@ function getAdapterFactory(vendor) {
74
90
  }
75
91
  return factory;
76
92
  }
93
+ function getVendorManifest(vendor) {
94
+ const manifest = registry.get(vendor)?.manifest ?? credentialVendors.get(vendor);
95
+ if (!manifest) {
96
+ const known = [...registry.keys(), ...credentialVendors.keys()].sort().join(", ") || "(none)";
97
+ throw new Error(`Unknown vendor '${vendor}'. Registered vendors: ${known}`);
98
+ }
99
+ return manifest;
100
+ }
77
101
  function listVendors() {
78
102
  return Array.from(registry.keys()).sort();
79
103
  }
80
104
  function getManifest() {
81
- const vendors = Array.from(registry.values()).map((f) => f.manifest).sort((a, b) => a.vendor.localeCompare(b.vendor));
105
+ const vendors = [
106
+ ...Array.from(registry.values()).map((f) => f.manifest),
107
+ ...credentialVendors.values()
108
+ ].sort((a, b) => a.vendor.localeCompare(b.vendor));
82
109
  return { vendors };
83
110
  }
84
111
 
@@ -3591,6 +3618,212 @@ var huggingfaceFactory = {
3591
3618
  create: () => new HuggingfaceAdapter()
3592
3619
  };
3593
3620
 
3621
+ // src/adapters/openrouter/manifest.ts
3622
+ var OPENROUTER_KNOWN_MODELS = [
3623
+ {
3624
+ id: "anthropic/claude-haiku-5.5",
3625
+ displayName: "Claude Haiku 5.5 (OpenRouter)",
3626
+ capabilities: {
3627
+ streaming: true,
3628
+ tools: true,
3629
+ vision: true,
3630
+ systemPrompt: true,
3631
+ maxContextTokens: 1e6
3632
+ }
3633
+ },
3634
+ {
3635
+ id: "anthropic/claude-sonnet-5.5",
3636
+ displayName: "Claude Sonnet 5.5 (OpenRouter)",
3637
+ capabilities: {
3638
+ streaming: true,
3639
+ tools: true,
3640
+ vision: true,
3641
+ systemPrompt: true,
3642
+ maxContextTokens: 1e6
3643
+ }
3644
+ },
3645
+ {
3646
+ id: "openai/gpt-5.4-mini",
3647
+ displayName: "GPT-5.4 Mini (OpenRouter)",
3648
+ capabilities: {
3649
+ streaming: true,
3650
+ tools: true,
3651
+ vision: true,
3652
+ systemPrompt: true,
3653
+ maxContextTokens: 4e5
3654
+ }
3655
+ },
3656
+ {
3657
+ id: "openai/gpt-4.1-mini",
3658
+ displayName: "GPT-4.1 Mini (OpenRouter)",
3659
+ capabilities: {
3660
+ streaming: true,
3661
+ tools: true,
3662
+ vision: true,
3663
+ systemPrompt: true,
3664
+ maxContextTokens: 1047576
3665
+ }
3666
+ },
3667
+ {
3668
+ id: "google/gemini-3.5-flash",
3669
+ displayName: "Gemini 3.5 Flash (OpenRouter)",
3670
+ capabilities: {
3671
+ streaming: true,
3672
+ tools: true,
3673
+ vision: true,
3674
+ systemPrompt: true,
3675
+ maxContextTokens: 1048576
3676
+ }
3677
+ },
3678
+ {
3679
+ id: "meta-llama/llama-3.3-70b-instruct",
3680
+ displayName: "Llama 3.3 70B Instruct (OpenRouter)",
3681
+ capabilities: {
3682
+ streaming: true,
3683
+ tools: true,
3684
+ vision: false,
3685
+ systemPrompt: true,
3686
+ maxContextTokens: 131072
3687
+ }
3688
+ }
3689
+ ];
3690
+ var openRouterManifest = {
3691
+ vendor: "openrouter",
3692
+ displayName: "OpenRouter",
3693
+ authKinds: [
3694
+ {
3695
+ kind: "apiKey",
3696
+ displayName: "API Key",
3697
+ fields: [
3698
+ {
3699
+ name: "apiKey",
3700
+ label: "API Key",
3701
+ type: "password",
3702
+ required: true,
3703
+ help: "Get an API key from https://openrouter.ai/settings/keys"
3704
+ },
3705
+ {
3706
+ name: "baseURL",
3707
+ label: "Base URL",
3708
+ type: "url",
3709
+ required: false,
3710
+ default: "https://openrouter.ai/api/v1",
3711
+ help: "Override only for proxies. Default points at OpenRouter's production endpoint."
3712
+ }
3713
+ ]
3714
+ }
3715
+ ],
3716
+ knownModels: OPENROUTER_KNOWN_MODELS,
3717
+ // /models is public; the adapter enriches it with OpenRouter's own capability metadata.
3718
+ supportsModelListing: true,
3719
+ docsUrl: "https://openrouter.ai/docs/api_reference/overview"
3720
+ };
3721
+
3722
+ // src/adapters/openrouter/adapter.ts
3723
+ var OPENROUTER_METADATA_EXTRACTOR = makeMetadataExtractor([
3724
+ { header: "x-generation-id", key: "request_id" }
3725
+ ]);
3726
+ var OPENROUTER_ATTRIBUTION_HEADERS = {
3727
+ "HTTP-Referer": "https://jambonz.org",
3728
+ "X-OpenRouter-Title": "jambonz"
3729
+ };
3730
+ var OpenRouterAdapter = class extends OpenAIAdapter {
3731
+ vendor = openRouterManifest.vendor;
3732
+ init(auth, client) {
3733
+ super.init(
3734
+ {
3735
+ ...auth,
3736
+ baseURL: auth.baseURL ?? "https://openrouter.ai/api/v1"
3737
+ },
3738
+ client
3739
+ );
3740
+ this.client = this.ensureClient().withOptions({
3741
+ defaultHeaders: OPENROUTER_ATTRIBUTION_HEADERS
3742
+ });
3743
+ }
3744
+ stream(req) {
3745
+ return streamFromOpenAI(this.ensureClient(), req, {
3746
+ knownModels: this.knownModels(),
3747
+ vendorMetadataExtractor: OPENROUTER_METADATA_EXTRACTOR,
3748
+ // Verified accepted by OpenRouter (forwarded to OpenAI-family upstreams).
3749
+ includeCacheKey: true,
3750
+ openRouterAutoCache: this.isOpenRouter
3751
+ });
3752
+ }
3753
+ async listAvailableModels() {
3754
+ const result = await this.ensureClient().models.list();
3755
+ const out = [];
3756
+ for (const m of result.data) {
3757
+ if (m.id.endsWith(":batch")) continue;
3758
+ const outputs = m.architecture?.output_modalities;
3759
+ if (outputs && !outputs.includes("text")) continue;
3760
+ const known = this.knownModels().find((k) => k.id === m.id);
3761
+ if (known) {
3762
+ out.push(known);
3763
+ continue;
3764
+ }
3765
+ out.push({
3766
+ id: m.id,
3767
+ ...m.name ? { displayName: m.name } : {},
3768
+ capabilities: {
3769
+ streaming: true,
3770
+ tools: m.supported_parameters?.includes("tools") ?? true,
3771
+ vision: m.architecture?.input_modalities?.includes("image") ?? false,
3772
+ systemPrompt: true,
3773
+ ...m.context_length ? { maxContextTokens: m.context_length } : {}
3774
+ }
3775
+ });
3776
+ }
3777
+ return out;
3778
+ }
3779
+ async testCredential() {
3780
+ await this.ensureClient().get("/key");
3781
+ }
3782
+ knownModels() {
3783
+ return openRouterManifest.knownModels;
3784
+ }
3785
+ };
3786
+
3787
+ // src/adapters/openrouter/factory.ts
3788
+ var openRouterFactory = {
3789
+ vendor: openRouterManifest.vendor,
3790
+ manifest: openRouterManifest,
3791
+ create: () => new OpenRouterAdapter()
3792
+ };
3793
+
3794
+ // src/vendors/harness.ts
3795
+ var harnessManifest = {
3796
+ vendor: "harness",
3797
+ displayName: "Harness",
3798
+ authKinds: [
3799
+ {
3800
+ // Only the form schema matters for a credential-only vendor; no
3801
+ // AuthSpec is ever built from it.
3802
+ kind: "apiKey",
3803
+ displayName: "API Key",
3804
+ fields: [
3805
+ {
3806
+ name: "url",
3807
+ label: "WebSocket URL",
3808
+ type: "url",
3809
+ required: true,
3810
+ help: "wss:// URL of your harness. ws:// is accepted only for a private or localhost address."
3811
+ },
3812
+ {
3813
+ name: "apiKey",
3814
+ label: "API Key",
3815
+ type: "password",
3816
+ required: true,
3817
+ help: 'Sent to the harness as "Authorization: Bearer <key>" when each call connects.'
3818
+ }
3819
+ ]
3820
+ }
3821
+ ],
3822
+ knownModels: [],
3823
+ supportsModelListing: false,
3824
+ credentialOnly: true
3825
+ };
3826
+
3594
3827
  // src/adapters/index.ts
3595
3828
  registerAdapter(openAIFactory);
3596
3829
  registerAdapter(deepseekFactory);
@@ -3607,6 +3840,8 @@ registerAdapter(bedrockFactory);
3607
3840
  registerAdapter(azureOpenAIFactory);
3608
3841
  registerAdapter(groqFactory);
3609
3842
  registerAdapter(huggingfaceFactory);
3843
+ registerAdapter(openRouterFactory);
3844
+ registerCredentialVendor(harnessManifest);
3610
3845
 
3611
3846
  // src/normalize-auth.ts
3612
3847
  function normalizeAuth(vendor, raw) {
@@ -3615,6 +3850,7 @@ function normalizeAuth(vendor, raw) {
3615
3850
  case "anthropic":
3616
3851
  case "groq":
3617
3852
  case "huggingface":
3853
+ case "openrouter":
3618
3854
  case "moonshot":
3619
3855
  case "zai":
3620
3856
  case "xai":
@@ -3753,8 +3989,7 @@ function parseServiceKey(value) {
3753
3989
 
3754
3990
  // src/pick-auth-kind.ts
3755
3991
  function pickAuthKind(vendor, raw) {
3756
- const factory = getAdapterFactory(vendor);
3757
- const { authKinds } = factory.manifest;
3992
+ const { authKinds } = getVendorManifest(vendor);
3758
3993
  if (authKinds.length === 1) {
3759
3994
  return authKinds[0];
3760
3995
  }
@@ -3815,6 +4050,7 @@ async function createLlm(args) {
3815
4050
  createLlm,
3816
4051
  getAdapterFactory,
3817
4052
  getManifest,
4053
+ getVendorManifest,
3818
4054
  listVendors,
3819
4055
  normalizeAuth,
3820
4056
  pickAuthKind,