@inkdropapp/ai 0.1.3 → 0.1.4

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
@@ -370,6 +370,51 @@ const settings: AISettings = {
370
370
  `openrouter` looks up `OPENROUTER_API_KEY`; `ollama_local` looks up
371
371
  `OLLAMA_LOCAL_API_KEY`. Each gets its own keyring entry under its `baseURL`.
372
372
 
373
+ ### Adding an OpenAI-compatible provider at runtime
374
+
375
+ Two-step flow: persist the new entry into `AISettings` and rebuild the
376
+ registry, then set the API key on the resulting provider instance.
377
+
378
+ ```ts
379
+ // 1. Add the entry to settings and persist (host owns the config layer).
380
+ const next: AISettings = {
381
+ ...settings,
382
+ providers: {
383
+ ...settings.providers,
384
+ openaiCompatible: [
385
+ ...(settings.providers.openaiCompatible ?? []),
386
+ {
387
+ id: 'groq',
388
+ baseURL: 'https://api.groq.com/openai/v1',
389
+ models: [{ id: 'llama-3.3-70b-versatile' }]
390
+ }
391
+ ]
392
+ }
393
+ }
394
+ writeConfigFile(next) // your persistence layer
395
+ registry.updateSettings(next)
396
+
397
+ // 2. The provider now exists in the registry — set its API key.
398
+ const provider = registry.getProvider('groq')!
399
+ await provider.setApiKey('gsk-...')
400
+ ```
401
+
402
+ `updateSettings` rebuilds the provider list but preserves the shared
403
+ `KeyStore`, so any keys already in the keyring stay resolved.
404
+
405
+ If your UX needs to **validate the key before persisting the entry**, you can
406
+ write directly to the `KeyStore` first — keys are stored against the
407
+ normalised `baseURL`, not the provider id, so this works without the entry
408
+ being registered yet:
409
+
410
+ ```ts
411
+ await registry.keyStore.setKey('https://api.groq.com/openai/v1', 'gsk-...')
412
+ // later, once validated: persist the AISettings entry and updateSettings.
413
+ ```
414
+
415
+ Once you add the entry and rebuild, `provider.isAuthenticated()` returns
416
+ `true` because the key already lives under that `baseURL`.
417
+
373
418
  ### Mixing slots across providers
374
419
 
375
420
  ```ts
package/package.json CHANGED
@@ -1,10 +1,20 @@
1
1
  {
2
2
  "name": "@inkdropapp/ai",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "AI integration common module",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
7
  "types": "types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./types/index.d.ts",
11
+ "default": "./src/index.ts"
12
+ },
13
+ "./constants": {
14
+ "types": "./types/provider-constants.d.ts",
15
+ "default": "./src/provider-constants.ts"
16
+ }
17
+ },
8
18
  "scripts": {
9
19
  "lint": "eslint src __tests__",
10
20
  "test": "node --experimental-vm-modules --no-warnings=ExperimentalWarning node_modules/jest/bin/jest.js --config jest.config.cjs --runInBand",
package/src/index.ts CHANGED
@@ -36,11 +36,17 @@ export {
36
36
  } from './errors.js'
37
37
  export { KeyStore, type KeyStoreOptions } from './key-store.js'
38
38
  export { normalizeBaseURL } from './url.js'
39
- export { AnthropicProvider } from './providers/anthropic.js'
40
39
  export {
41
- OpenAICompatibleProvider,
40
+ ANTHROPIC_PROVIDER_ID,
41
+ ANTHROPIC_PROVIDER_NAME,
42
+ ANTHROPIC_ENV_VAR,
43
+ ANTHROPIC_DEFAULT_BASE_URL,
44
+ OPENAI_COMPATIBLE_PROVIDER_ID,
45
+ OPENAI_COMPATIBLE_PROVIDER_NAME,
42
46
  deriveEnvVarName
43
- } from './providers/openai-compatible.js'
47
+ } from './provider-constants.js'
48
+ export { AnthropicProvider } from './providers/anthropic.js'
49
+ export { OpenAICompatibleProvider } from './providers/openai-compatible.js'
44
50
  export {
45
51
  AIRegistry,
46
52
  type AIRegistryOptions,
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Pure-data provider metadata for settings UIs.
3
+ *
4
+ * This module has no class or native-module imports, so it's safe to load in
5
+ * an Electron renderer process — unlike the main package entrypoint, which
6
+ * transitively pulls in `@napi-rs/keyring`.
7
+ *
8
+ * Consume via the `@inkdropapp/ai/constants` subpath:
9
+ *
10
+ * import {
11
+ * ANTHROPIC_PROVIDER_ID,
12
+ * ANTHROPIC_PROVIDER_NAME,
13
+ * ANTHROPIC_ENV_VAR,
14
+ * ANTHROPIC_DEFAULT_BASE_URL,
15
+ * OPENAI_COMPATIBLE_PROVIDER_ID,
16
+ * OPENAI_COMPATIBLE_PROVIDER_NAME,
17
+ * deriveEnvVarName
18
+ * } from '@inkdropapp/ai/constants'
19
+ */
20
+
21
+ export const ANTHROPIC_PROVIDER_ID = 'anthropic'
22
+ export const ANTHROPIC_PROVIDER_NAME = 'Anthropic'
23
+ export const ANTHROPIC_ENV_VAR = 'ANTHROPIC_API_KEY'
24
+ export const ANTHROPIC_DEFAULT_BASE_URL = 'https://api.anthropic.com'
25
+
26
+ /**
27
+ * The `AISettings.providers` key for OpenAI-compatible entries. Use this to
28
+ * tag a settings-UI row as "an OpenAI-compatible provider" (vs. the built-in
29
+ * Anthropic row).
30
+ *
31
+ * Runtime provider ids for OpenAI-compatible entries are user-chosen
32
+ * (`AISettings.providers.openaiCompatible[].id`); this constant is the
33
+ * *type/kind*, not an instance id.
34
+ */
35
+ export const OPENAI_COMPATIBLE_PROVIDER_ID = 'openaiCompatible'
36
+ export const OPENAI_COMPATIBLE_PROVIDER_NAME = 'OpenAI-Compatible'
37
+
38
+ /**
39
+ * Derives the env-var name for an OpenAI-compatible provider entry from its
40
+ * user-chosen `id`.
41
+ *
42
+ * Rules: replace any non-alphanumeric character with `_`, collapse runs of `_`,
43
+ * uppercase, append `_API_KEY`.
44
+ *
45
+ * Examples:
46
+ * `openrouter` → `OPENROUTER_API_KEY`
47
+ * `together_ai` → `TOGETHER_AI_API_KEY`
48
+ * `ollama-local` → `OLLAMA_LOCAL_API_KEY`
49
+ * `my.proxy` → `MY_PROXY_API_KEY`
50
+ */
51
+ export const deriveEnvVarName = (id: string): string => {
52
+ const sanitized = id
53
+ .replace(/[^a-zA-Z0-9]+/g, '_')
54
+ .replace(/^_+|_+$/g, '')
55
+ .toUpperCase()
56
+ return `${sanitized}_API_KEY`
57
+ }
@@ -18,6 +18,12 @@ import { NoApiKey } from '../errors.js'
18
18
  import type { KeyStore } from '../key-store.js'
19
19
  import { mapAiSdkError } from '../internal/map-ai-sdk-error.js'
20
20
  import { mapAiSdkStream } from '../internal/map-ai-sdk-stream.js'
21
+ import {
22
+ ANTHROPIC_DEFAULT_BASE_URL,
23
+ ANTHROPIC_ENV_VAR,
24
+ ANTHROPIC_PROVIDER_ID,
25
+ ANTHROPIC_PROVIDER_NAME
26
+ } from '../provider-constants.js'
21
27
  import type { AIModel, AIProvider, CompletionRequest } from '../provider.js'
22
28
  import type {
23
29
  AnthropicModelConfig,
@@ -25,11 +31,6 @@ import type {
25
31
  } from '../settings.js'
26
32
  import type { StreamEvent } from '../stream-events.js'
27
33
 
28
- const PROVIDER_ID = 'anthropic'
29
- const PROVIDER_NAME = 'Anthropic'
30
- const ENV_VAR = 'ANTHROPIC_API_KEY'
31
- const DEFAULT_BASE_URL = 'https://api.anthropic.com'
32
-
33
34
  /**
34
35
  * The Anthropic (Claude) provider.
35
36
  *
@@ -45,9 +46,9 @@ const DEFAULT_BASE_URL = 'https://api.anthropic.com'
45
46
  * `clearApiKey` is called or the resolved API key changes.
46
47
  */
47
48
  export class AnthropicProvider implements AIProvider {
48
- readonly id = PROVIDER_ID
49
- readonly name = PROVIDER_NAME
50
- readonly envVarName = ENV_VAR
49
+ readonly id = ANTHROPIC_PROVIDER_ID
50
+ readonly name = ANTHROPIC_PROVIDER_NAME
51
+ readonly envVarName = ANTHROPIC_ENV_VAR
51
52
  readonly baseURL: string
52
53
 
53
54
  private readonly keyStore: KeyStore
@@ -64,7 +65,7 @@ export class AnthropicProvider implements AIProvider {
64
65
  catalog?: AnthropicCatalog
65
66
  ) {
66
67
  this.keyStore = keyStore
67
- this.baseURL = config.baseURL ?? DEFAULT_BASE_URL
68
+ this.baseURL = config.baseURL ?? ANTHROPIC_DEFAULT_BASE_URL
68
69
 
69
70
  const baseModels = catalog?.models ?? ANTHROPIC_MODELS
70
71
  this.defaultId = catalog?.defaultModelId ?? ANTHROPIC_DEFAULT_MODEL_ID
@@ -11,6 +11,7 @@ import { NoApiKey } from '../errors.js'
11
11
  import type { KeyStore } from '../key-store.js'
12
12
  import { mapAiSdkError } from '../internal/map-ai-sdk-error.js'
13
13
  import { mapAiSdkStream } from '../internal/map-ai-sdk-stream.js'
14
+ import { deriveEnvVarName } from '../provider-constants.js'
14
15
  import type { AIModel, AIProvider, CompletionRequest } from '../provider.js'
15
16
  import type {
16
17
  OpenAICompatibleModelConfig,
@@ -26,26 +27,6 @@ const DEFAULT_CAPABILITIES: ModelCapabilities = {
26
27
  maxTokens: 32_000
27
28
  }
28
29
 
29
- /**
30
- * Derives the env-var name for a user-named provider entry.
31
- *
32
- * Rules: replace any non-alphanumeric character with `_`, collapse runs of `_`,
33
- * uppercase, append `_API_KEY`.
34
- *
35
- * Examples:
36
- * `openrouter` → `OPENROUTER_API_KEY`
37
- * `together_ai` → `TOGETHER_AI_API_KEY`
38
- * `ollama-local` → `OLLAMA_LOCAL_API_KEY`
39
- * `my.proxy` → `MY_PROXY_API_KEY`
40
- */
41
- export const deriveEnvVarName = (id: string): string => {
42
- const sanitized = id
43
- .replace(/[^a-zA-Z0-9]+/g, '_')
44
- .replace(/^_+|_+$/g, '')
45
- .toUpperCase()
46
- return `${sanitized}_API_KEY`
47
- }
48
-
49
30
  const mergeCapabilities = (
50
31
  override: Partial<ModelCapabilities> | undefined
51
32
  ): ModelCapabilities => ({
package/types/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export { ANTHROPIC_MODELS, ANTHROPIC_DEFAULT_MODEL_ID, ANTHROPIC_DEFAULT_FAST_MO
5
5
  export { AIError, NoApiKey, AuthenticationError, RateLimitExceeded, ServerOverloaded, PromptTooLarge, UpstreamError, isAIError, type AIErrorKind } from './errors.js';
6
6
  export { KeyStore, type KeyStoreOptions } from './key-store.js';
7
7
  export { normalizeBaseURL } from './url.js';
8
+ export { ANTHROPIC_PROVIDER_ID, ANTHROPIC_PROVIDER_NAME, ANTHROPIC_ENV_VAR, ANTHROPIC_DEFAULT_BASE_URL, OPENAI_COMPATIBLE_PROVIDER_ID, OPENAI_COMPATIBLE_PROVIDER_NAME, deriveEnvVarName } from './provider-constants.js';
8
9
  export { AnthropicProvider } from './providers/anthropic.js';
9
- export { OpenAICompatibleProvider, deriveEnvVarName } from './providers/openai-compatible.js';
10
+ export { OpenAICompatibleProvider } from './providers/openai-compatible.js';
10
11
  export { AIRegistry, type AIRegistryOptions, type ResolvedSlot } from './registry.js';
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Pure-data provider metadata for settings UIs.
3
+ *
4
+ * This module has no class or native-module imports, so it's safe to load in
5
+ * an Electron renderer process — unlike the main package entrypoint, which
6
+ * transitively pulls in `@napi-rs/keyring`.
7
+ *
8
+ * Consume via the `@inkdropapp/ai/constants` subpath:
9
+ *
10
+ * import {
11
+ * ANTHROPIC_PROVIDER_ID,
12
+ * ANTHROPIC_PROVIDER_NAME,
13
+ * ANTHROPIC_ENV_VAR,
14
+ * ANTHROPIC_DEFAULT_BASE_URL,
15
+ * OPENAI_COMPATIBLE_PROVIDER_ID,
16
+ * OPENAI_COMPATIBLE_PROVIDER_NAME,
17
+ * deriveEnvVarName
18
+ * } from '@inkdropapp/ai/constants'
19
+ */
20
+ export declare const ANTHROPIC_PROVIDER_ID = "anthropic";
21
+ export declare const ANTHROPIC_PROVIDER_NAME = "Anthropic";
22
+ export declare const ANTHROPIC_ENV_VAR = "ANTHROPIC_API_KEY";
23
+ export declare const ANTHROPIC_DEFAULT_BASE_URL = "https://api.anthropic.com";
24
+ /**
25
+ * The `AISettings.providers` key for OpenAI-compatible entries. Use this to
26
+ * tag a settings-UI row as "an OpenAI-compatible provider" (vs. the built-in
27
+ * Anthropic row).
28
+ *
29
+ * Runtime provider ids for OpenAI-compatible entries are user-chosen
30
+ * (`AISettings.providers.openaiCompatible[].id`); this constant is the
31
+ * *type/kind*, not an instance id.
32
+ */
33
+ export declare const OPENAI_COMPATIBLE_PROVIDER_ID = "openaiCompatible";
34
+ export declare const OPENAI_COMPATIBLE_PROVIDER_NAME = "OpenAI-Compatible";
35
+ /**
36
+ * Derives the env-var name for an OpenAI-compatible provider entry from its
37
+ * user-chosen `id`.
38
+ *
39
+ * Rules: replace any non-alphanumeric character with `_`, collapse runs of `_`,
40
+ * uppercase, append `_API_KEY`.
41
+ *
42
+ * Examples:
43
+ * `openrouter` → `OPENROUTER_API_KEY`
44
+ * `together_ai` → `TOGETHER_AI_API_KEY`
45
+ * `ollama-local` → `OLLAMA_LOCAL_API_KEY`
46
+ * `my.proxy` → `MY_PROXY_API_KEY`
47
+ */
48
+ export declare const deriveEnvVarName: (id: string) => string;
@@ -2,19 +2,6 @@ import { type OpenAICompatibleProvider as SdkOpenAICompatibleProvider } from '@a
2
2
  import type { KeyStore } from '../key-store.js';
3
3
  import type { AIModel, AIProvider } from '../provider.js';
4
4
  import type { OpenAICompatibleProviderConfig } from '../settings.js';
5
- /**
6
- * Derives the env-var name for a user-named provider entry.
7
- *
8
- * Rules: replace any non-alphanumeric character with `_`, collapse runs of `_`,
9
- * uppercase, append `_API_KEY`.
10
- *
11
- * Examples:
12
- * `openrouter` → `OPENROUTER_API_KEY`
13
- * `together_ai` → `TOGETHER_AI_API_KEY`
14
- * `ollama-local` → `OLLAMA_LOCAL_API_KEY`
15
- * `my.proxy` → `MY_PROXY_API_KEY`
16
- */
17
- export declare const deriveEnvVarName: (id: string) => string;
18
5
  /**
19
6
  * Provider for any OpenAI-compatible chat-completions endpoint
20
7
  * (OpenRouter, Together, Fireworks, Groq, vLLM, Ollama via `/v1`, LiteLLM, …).