agentic-kit 1.0.3 → 1.2.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.
package/README.md CHANGED
@@ -12,7 +12,14 @@
12
12
  <a href="https://www.npmjs.com/package/agentic-kit"><img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/agentic-kit?filename=packages%2Fagentic-kit%2Fpackage.json"/></a>
13
13
  </p>
14
14
 
15
- A unified, streaming-capable interface for multiple LLM providers. Plug in any supported adapter and swap between them at runtime.
15
+ A low-level provider portability layer for LLM applications. `agentic-kit`
16
+ provides:
17
+
18
+ - provider-independent `ModelDescriptor` and `Context` types
19
+ - structured streaming events for text, reasoning, and tool calls
20
+ - model and provider registries
21
+ - cross-provider message normalization for replay and handoff
22
+ - a one-release compatibility wrapper for the legacy `AgentKit.generate()` API
16
23
 
17
24
  ## Installation
18
25
 
@@ -23,58 +30,94 @@ npm install agentic-kit
23
30
 
24
31
  ## Quick Start
25
32
 
33
+ ### Structured API
34
+
26
35
  ```typescript
27
- import { createOllamaKit, createMultiProviderKit, OllamaAdapter, AgentKit } from 'agentic-kit';
36
+ import { complete, getModel } from 'agentic-kit';
28
37
 
29
- // Ollama
30
- const kit = createOllamaKit('http://localhost:11434');
31
- const text = await kit.generate({ model: 'mistral', prompt: 'Hello' });
38
+ const model = getModel('openai', 'gpt-4o-mini');
39
+ const message = await complete(model!, {
40
+ messages: [{ role: 'user', content: 'Hello', timestamp: Date.now() }],
41
+ });
32
42
 
33
- // Multi-provider with fallback
34
- const multi = createMultiProviderKit();
35
- multi.addProvider(new OllamaAdapter('http://localhost:11434'));
36
- const reply = await multi.generate({ model: 'mistral', prompt: 'Hello' });
43
+ console.log(message.content);
37
44
  ```
38
45
 
39
- ## Streaming
46
+ ### Streaming
40
47
 
41
48
  ```typescript
42
- await kit.generate(
43
- { model: 'mistral', prompt: 'Hello', stream: true },
44
- { onChunk: (chunk) => process.stdout.write(chunk) }
45
- );
49
+ import { stream, getModel } from 'agentic-kit';
50
+
51
+ const model = getModel('openai', 'gpt-4o-mini');
52
+ const result = stream(model!, {
53
+ messages: [{ role: 'user', content: 'Explain tool calling briefly.', timestamp: Date.now() }],
54
+ });
55
+
56
+ for await (const event of result) {
57
+ if (event.type === 'text_delta') {
58
+ process.stdout.write(event.delta);
59
+ }
60
+ }
46
61
  ```
47
62
 
48
63
  ## API Reference
49
64
 
50
- ### `AgentKit`
65
+ ### Core API
66
+
67
+ - `stream(model: ModelDescriptor, context: Context, options?: StreamOptions)`
68
+ - `complete(model: ModelDescriptor, context: Context, options?: StreamOptions)`
69
+ - `completeText(model: ModelDescriptor, context: Context, options?: StreamOptions)`
70
+
71
+ ### Registry
72
+
73
+ - `registerModel(model)`, `registerModels(models)`, `clearModels()`
74
+ - `registerProvider(provider, sourceId?)`, `unregisterProviders(sourceId)`, `clearProviders()`
75
+ - `getModel(provider, modelId)`, `getModels(provider?)`, `getModelProviders()`
76
+ - `getProvider(api)`, `getRegisteredProviders()`
77
+
78
+ The package pre-registers `OpenAIAdapter`, `AnthropicAdapter`, and
79
+ `OllamaAdapter` (with empty credentials) plus the built-in model catalogs
80
+ from each adapter package. Re-register an adapter with your own API key to
81
+ activate it.
51
82
 
52
- - `.generate(input: GenerateInput, options?: StreamingOptions): Promise<string | void>`
53
- - `.addProvider(provider: AgentProvider): void`
54
- - `.setProvider(name: string): void`
55
- - `.listProviders(): string[]`
56
- - `.getCurrentProvider(): AgentProvider | undefined`
83
+ ### Message helpers
57
84
 
58
- ### `GenerateInput`
85
+ - `createUserMessage(content)`, `createAssistantMessage(model)`,
86
+ `createToolResultMessage(toolCallId, toolName, content, isError?)`
87
+ - `createTextContent(text?)`, `createImageContent(data, mimeType)`,
88
+ `createToolCall(id, name)`
89
+ - `getMessageText(assistant)` — concatenate text blocks.
90
+ - `cloneMessage(message)` — deep clone.
91
+ - `normalizeContext(context)` — apply per-message normalization.
92
+ - `injectDeferralResults(messages, options?)` — synthesize stand-in
93
+ `toolResult` messages for every `toolCall` that lacks both a decision and
94
+ a paired result. Useful when the user types a new message instead of
95
+ responding to a paused tool — the next request needs a well-formed
96
+ transcript.
97
+ - `transformMessages(messages, model)` — cross-provider normalization for
98
+ replay across different providers.
99
+
100
+ ### Adapter re-exports
101
+
102
+ - `OpenAIAdapter`, `AnthropicAdapter`, `OllamaAdapter`
103
+ - Types: `OpenAIOptions`, `AnthropicOptions`
104
+ - `OllamaClient` for direct Ollama HTTP access.
105
+
106
+ ### Legacy Compatibility API
107
+
108
+ `AgentKit` is still available for one transition release:
59
109
 
60
110
  ```ts
61
111
  interface GenerateInput {
62
112
  model: string;
63
- prompt: string;
113
+ prompt?: string;
114
+ messages?: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>;
64
115
  stream?: boolean;
65
116
  }
66
117
  ```
67
118
 
68
- ### `StreamingOptions`
69
-
70
- ```ts
71
- interface StreamingOptions {
72
- onChunk?: (chunk: string) => void;
73
- onStateChange?: (state: string) => void;
74
- onError?: (error: Error) => void;
75
- onComplete?: () => void;
76
- }
77
- ```
119
+ Use `createOpenAIKit`, `createAnthropicKit`, `createOllamaKit`, or
120
+ `createMultiProviderKit()` if you still need the old prompt-only entrypoint.
78
121
 
79
122
  ---
80
123
 
@@ -0,0 +1,77 @@
1
+ export class EventStream {
2
+ isTerminal;
3
+ extractResult;
4
+ queue = [];
5
+ waiting = [];
6
+ done = false;
7
+ finalResultPromise;
8
+ resolveFinalResult;
9
+ constructor(isTerminal, extractResult) {
10
+ this.isTerminal = isTerminal;
11
+ this.extractResult = extractResult;
12
+ this.finalResultPromise = new Promise((resolve) => {
13
+ this.resolveFinalResult = resolve;
14
+ });
15
+ }
16
+ push(event) {
17
+ if (this.done) {
18
+ return;
19
+ }
20
+ if (this.isTerminal(event)) {
21
+ this.done = true;
22
+ this.resolveFinalResult(this.extractResult(event));
23
+ }
24
+ const waiter = this.waiting.shift();
25
+ if (waiter) {
26
+ waiter({ value: event, done: false });
27
+ return;
28
+ }
29
+ this.queue.push(event);
30
+ }
31
+ end(result) {
32
+ this.done = true;
33
+ if (result !== undefined) {
34
+ this.resolveFinalResult(result);
35
+ }
36
+ while (this.waiting.length > 0) {
37
+ this.waiting.shift()({ value: undefined, done: true });
38
+ }
39
+ }
40
+ async *[Symbol.asyncIterator]() {
41
+ while (true) {
42
+ if (this.queue.length > 0) {
43
+ yield this.queue.shift();
44
+ continue;
45
+ }
46
+ if (this.done) {
47
+ return;
48
+ }
49
+ const next = await new Promise((resolve) => {
50
+ this.waiting.push(resolve);
51
+ });
52
+ if (next.done) {
53
+ return;
54
+ }
55
+ yield next.value;
56
+ }
57
+ }
58
+ result() {
59
+ return this.finalResultPromise;
60
+ }
61
+ }
62
+ export class DefaultAssistantMessageEventStream extends EventStream {
63
+ constructor() {
64
+ super((event) => event.type === 'done' || event.type === 'error', (event) => {
65
+ if (event.type === 'done') {
66
+ return event.message;
67
+ }
68
+ if (event.type === 'error') {
69
+ return event.error;
70
+ }
71
+ throw new Error('Unexpected terminal event');
72
+ });
73
+ }
74
+ }
75
+ export function createAssistantMessageEventStream() {
76
+ return new DefaultAssistantMessageEventStream();
77
+ }
package/esm/index.js CHANGED
@@ -1,74 +1,109 @@
1
- import OllamaClient from '@agentic-kit/ollama';
2
- import { AnthropicAdapter } from '@agentic-kit/anthropic';
3
- import { OpenAIAdapter } from '@agentic-kit/openai';
4
- export { OllamaClient };
5
- export { AnthropicAdapter };
6
- export { OpenAIAdapter };
7
- // ─── Ollama Adapter ───────────────────────────────────────────────────────────
8
- export class OllamaAdapter {
9
- name = 'ollama';
10
- client;
11
- constructor(baseUrl) {
12
- this.client = new OllamaClient(baseUrl);
13
- }
14
- async generate(input) {
15
- return this.client.generate(input);
16
- }
17
- async generateStreaming(input, onChunk) {
18
- return this.client.generate(input, onChunk);
19
- }
20
- async listModels() {
21
- return this.client.listModels();
1
+ import { ANTHROPIC_MODELS, AnthropicAdapter } from '@agentic-kit/anthropic';
2
+ import { OLLAMA_MODELS, OllamaAdapter, OllamaClient } from '@agentic-kit/ollama';
3
+ import { OPENAI_COMPATIBLE_MODELS, OpenAIAdapter, } from '@agentic-kit/openai';
4
+ import { createAssistantMessageEventStream, EventStream } from './event-stream.js';
5
+ import { getMessageText, normalizeContext } from './messages.js';
6
+ import { clearModels, getModel, getModels, getProviders as getModelProviders, registerModel, registerModels, } from './model-registry.js';
7
+ import { clearProviders, getProvider as getRegisteredProvider, getProviders as getRegisteredProviders, registerProvider, unregisterProviders, } from './provider-registry.js';
8
+ import { transformMessages } from './transform-messages.js';
9
+ export * from './event-stream.js';
10
+ export * from './messages.js';
11
+ export * from './transform-messages.js';
12
+ export * from './types.js';
13
+ export { createAssistantMessageEventStream, EventStream, OllamaClient };
14
+ export { AnthropicAdapter, OllamaAdapter, OpenAIAdapter };
15
+ registerModels([...OPENAI_COMPATIBLE_MODELS, ...ANTHROPIC_MODELS, ...OLLAMA_MODELS]);
16
+ registerProvider(new OpenAIAdapter());
17
+ registerProvider(new AnthropicAdapter({ apiKey: '' }));
18
+ registerProvider(new OllamaAdapter());
19
+ export function stream(model, context, options) {
20
+ const provider = getRegisteredProvider(model.api);
21
+ if (!provider) {
22
+ throw new Error(`No provider registered for api '${model.api}'`);
22
23
  }
24
+ return streamWithProvider(provider, model, context, options);
25
+ }
26
+ export async function complete(model, context, options) {
27
+ const response = stream(model, context, options);
28
+ return response.result();
29
+ }
30
+ export async function completeText(model, context, options) {
31
+ const message = await complete(model, context, options);
32
+ return getMessageText(message);
23
33
  }
24
- // ─── AgentKit ─────────────────────────────────────────────────────────────────
25
34
  export class AgentKit {
26
35
  providers = new Map();
27
36
  current;
28
37
  addProvider(provider) {
29
- this.providers.set(provider.name, provider);
30
- if (!this.current)
38
+ this.providers.set(getProviderName(provider), provider);
39
+ if (!this.current) {
31
40
  this.current = provider;
41
+ }
32
42
  return this;
33
43
  }
34
44
  setProvider(name) {
35
45
  const provider = this.providers.get(name);
36
- if (!provider)
46
+ if (!provider) {
37
47
  throw new Error(`Provider '${name}' not found`);
48
+ }
38
49
  this.current = provider;
39
50
  return this;
40
51
  }
41
- getCurrentProvider() { return this.current; }
42
- listProviders() { return Array.from(this.providers.keys()); }
52
+ getCurrentProvider() {
53
+ return this.current;
54
+ }
55
+ listProviders() {
56
+ return Array.from(this.providers.keys());
57
+ }
43
58
  async generate(input, options) {
44
- if (!this.current)
59
+ if (!this.current) {
45
60
  throw new Error('No provider set. Call addProvider() first.');
46
- if (options?.onChunk) {
61
+ }
62
+ const provider = this.current;
63
+ const model = createLegacyModel(provider, input.model, input.maxTokens);
64
+ const context = legacyInputToContext(input);
65
+ const streamOptions = {
66
+ maxTokens: input.maxTokens,
67
+ temperature: input.temperature,
68
+ };
69
+ if (options?.onChunk || input.stream) {
70
+ options?.onStateChange?.('streaming');
47
71
  try {
48
- await this.current.generateStreaming(input, options.onChunk);
49
- options.onComplete?.();
72
+ const response = streamWithProvider(provider, model, context, streamOptions);
73
+ for await (const event of response) {
74
+ if (event.type === 'text_delta') {
75
+ options?.onChunk?.(event.delta);
76
+ }
77
+ }
78
+ assertLegacyGenerateSucceeded(await response.result());
79
+ options?.onComplete?.();
50
80
  }
51
- catch (err) {
52
- options.onError?.(err);
53
- throw err;
81
+ catch (error) {
82
+ options?.onError?.(error);
83
+ throw error;
54
84
  }
55
85
  return;
56
86
  }
57
87
  try {
58
- const result = await this.current.generate(input);
88
+ const message = assertLegacyGenerateSucceeded(await completeWithProvider(provider, model, context, streamOptions));
89
+ options?.onStateChange?.('complete');
90
+ const text = getMessageText(message);
59
91
  options?.onComplete?.();
60
- return result;
92
+ return text;
61
93
  }
62
- catch (err) {
63
- options?.onError?.(err);
64
- throw err;
94
+ catch (error) {
95
+ options?.onError?.(error);
96
+ throw error;
65
97
  }
66
98
  }
67
99
  async listModels() {
68
- return this.current?.listModels?.() ?? [];
100
+ if (!this.current?.listModels) {
101
+ return [];
102
+ }
103
+ const models = await this.current.listModels();
104
+ return models.map((model) => (typeof model === 'string' ? model : model.id));
69
105
  }
70
106
  }
71
- // ─── Factory helpers ──────────────────────────────────────────────────────────
72
107
  export function createOllamaKit(baseUrl) {
73
108
  return new AgentKit().addProvider(new OllamaAdapter(baseUrl));
74
109
  }
@@ -81,3 +116,78 @@ export function createOpenAIKit(options) {
81
116
  export function createMultiProviderKit() {
82
117
  return new AgentKit();
83
118
  }
119
+ export { clearModels, clearProviders, getModel, getModelProviders, getModels, getRegisteredProvider as getProvider, getRegisteredProviders, registerModel, registerModels, registerProvider, unregisterProviders, };
120
+ function legacyInputToContext(input) {
121
+ const messages = input.messages
122
+ ? input.messages
123
+ .filter((message) => message.role !== 'system')
124
+ .map((message) => message.role === 'assistant'
125
+ ? {
126
+ role: 'assistant',
127
+ api: 'legacy',
128
+ provider: 'legacy',
129
+ model: input.model,
130
+ content: [{ type: 'text', text: message.content }],
131
+ usage: {
132
+ input: 0,
133
+ output: 0,
134
+ cacheRead: 0,
135
+ cacheWrite: 0,
136
+ totalTokens: 0,
137
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
138
+ },
139
+ stopReason: 'stop',
140
+ timestamp: Date.now(),
141
+ }
142
+ : {
143
+ role: 'user',
144
+ content: message.content,
145
+ timestamp: Date.now(),
146
+ })
147
+ : [{ role: 'user', content: input.prompt ?? '', timestamp: Date.now() }];
148
+ const systemPrompt = input.system ?? input.messages?.find((message) => message.role === 'system')?.content;
149
+ return {
150
+ systemPrompt,
151
+ messages,
152
+ };
153
+ }
154
+ function createLegacyModel(provider, modelId, maxTokens) {
155
+ if (provider.createModel) {
156
+ return provider.createModel(modelId, {
157
+ maxOutputTokens: maxTokens,
158
+ });
159
+ }
160
+ return {
161
+ id: modelId,
162
+ name: modelId,
163
+ api: provider.api,
164
+ provider: getProviderName(provider),
165
+ baseUrl: '',
166
+ input: ['text'],
167
+ reasoning: false,
168
+ tools: true,
169
+ maxOutputTokens: maxTokens,
170
+ };
171
+ }
172
+ function streamWithProvider(provider, model, context, options) {
173
+ const normalized = normalizeContext(context);
174
+ const transformedContext = {
175
+ ...normalized,
176
+ messages: transformMessages(normalized.messages, model),
177
+ };
178
+ return provider.stream(model, transformedContext, options);
179
+ }
180
+ async function completeWithProvider(provider, model, context, options) {
181
+ const response = streamWithProvider(provider, model, context, options);
182
+ return response.result();
183
+ }
184
+ function getProviderName(provider) {
185
+ return provider.name ?? provider.provider;
186
+ }
187
+ function assertLegacyGenerateSucceeded(message) {
188
+ if (message.stopReason === 'error' || message.stopReason === 'aborted') {
189
+ throw new Error(message.errorMessage ??
190
+ (message.stopReason === 'aborted' ? 'Generation aborted.' : 'Generation failed.'), { cause: message });
191
+ }
192
+ return message;
193
+ }
@@ -0,0 +1,130 @@
1
+ export function createEmptyUsage() {
2
+ return {
3
+ input: 0,
4
+ output: 0,
5
+ cacheRead: 0,
6
+ cacheWrite: 0,
7
+ totalTokens: 0,
8
+ cost: {
9
+ input: 0,
10
+ output: 0,
11
+ cacheRead: 0,
12
+ cacheWrite: 0,
13
+ total: 0,
14
+ },
15
+ };
16
+ }
17
+ export function calculateUsageCost(model, usage) {
18
+ const schedule = model.cost;
19
+ usage.cost.input = ((schedule?.input ?? 0) / 1_000_000) * usage.input;
20
+ usage.cost.output = ((schedule?.output ?? 0) / 1_000_000) * usage.output;
21
+ usage.cost.cacheRead = ((schedule?.cacheRead ?? 0) / 1_000_000) * usage.cacheRead;
22
+ usage.cost.cacheWrite = ((schedule?.cacheWrite ?? 0) / 1_000_000) * usage.cacheWrite;
23
+ usage.cost.total =
24
+ usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
25
+ return usage.cost;
26
+ }
27
+ export function createAssistantMessage(model) {
28
+ return {
29
+ role: 'assistant',
30
+ api: model.api,
31
+ provider: model.provider,
32
+ model: model.id,
33
+ content: [],
34
+ usage: createEmptyUsage(),
35
+ stopReason: 'stop',
36
+ timestamp: Date.now(),
37
+ };
38
+ }
39
+ export function createTextContent(text = '') {
40
+ return { type: 'text', text };
41
+ }
42
+ export function createImageContent(data, mimeType) {
43
+ return { type: 'image', data, mimeType };
44
+ }
45
+ export function createToolCall(id, name) {
46
+ return { type: 'toolCall', id, name, arguments: {}, rawArguments: '' };
47
+ }
48
+ export function createUserMessage(content) {
49
+ return {
50
+ role: 'user',
51
+ content,
52
+ timestamp: Date.now(),
53
+ };
54
+ }
55
+ export function createToolResultMessage(toolCallId, toolName, content, isError = false) {
56
+ return {
57
+ role: 'toolResult',
58
+ toolCallId,
59
+ toolName,
60
+ content,
61
+ isError,
62
+ timestamp: Date.now(),
63
+ };
64
+ }
65
+ export function getMessageText(message) {
66
+ return message.content
67
+ .filter((block) => block.type === 'text')
68
+ .map((block) => block.text)
69
+ .join('');
70
+ }
71
+ const DEFAULT_DEFERRAL_TEXT = 'User did not respond. Continue.';
72
+ /**
73
+ * Synthesize toolResult messages for every toolCall that lacks both a decision
74
+ * and a paired toolResult. Returns input unchanged (referentially equal) when
75
+ * nothing needs synthesizing.
76
+ *
77
+ * Use case: user types a message instead of clicking approve/deny on a paused
78
+ * tool. The agent can't resume on text alone — every dangling toolCall needs
79
+ * a result before the next request. Compose with sendMessages to forward the
80
+ * conversation cleanly:
81
+ *
82
+ * await sendMessages([...injectDeferralResults(messages), createUserMessage(text)]);
83
+ */
84
+ export function injectDeferralResults(messages, optionsOrText = {}) {
85
+ const opts = typeof optionsOrText === 'string' ? { deferralText: optionsOrText } : optionsOrText;
86
+ const deferralText = opts.deferralText ?? DEFAULT_DEFERRAL_TEXT;
87
+ const completed = new Set();
88
+ for (const m of messages) {
89
+ if (m.role === 'toolResult')
90
+ completed.add(m.toolCallId);
91
+ }
92
+ const synthetic = [];
93
+ for (const m of messages) {
94
+ if (m.role !== 'assistant')
95
+ continue;
96
+ for (const block of m.content) {
97
+ if (block.type !== 'toolCall')
98
+ continue;
99
+ if (completed.has(block.id))
100
+ continue;
101
+ if ('decision' in block && block.decision !== undefined)
102
+ continue;
103
+ const result = createToolResultMessage(block.id, block.name, [
104
+ { type: 'text', text: deferralText },
105
+ ]);
106
+ if (opts.details !== undefined)
107
+ result.details = opts.details;
108
+ synthetic.push(result);
109
+ }
110
+ }
111
+ if (synthetic.length === 0)
112
+ return messages;
113
+ return [...messages, ...synthetic];
114
+ }
115
+ export function cloneMessage(message) {
116
+ return JSON.parse(JSON.stringify(message));
117
+ }
118
+ export function normalizeContext(context) {
119
+ return {
120
+ systemPrompt: context.systemPrompt,
121
+ tools: context.tools ?? [],
122
+ messages: context.messages.map((message) => ensureTimestamp(cloneMessage(message))),
123
+ };
124
+ }
125
+ function ensureTimestamp(message) {
126
+ if (typeof message.timestamp !== 'number') {
127
+ message.timestamp = Date.now();
128
+ }
129
+ return message;
130
+ }
@@ -0,0 +1,26 @@
1
+ const modelsByProvider = new Map();
2
+ export function registerModel(model) {
3
+ const providerModels = modelsByProvider.get(model.provider) ?? new Map();
4
+ providerModels.set(model.id, model);
5
+ modelsByProvider.set(model.provider, providerModels);
6
+ }
7
+ export function registerModels(models) {
8
+ for (const model of models) {
9
+ registerModel(model);
10
+ }
11
+ }
12
+ export function getModel(provider, modelId) {
13
+ return modelsByProvider.get(provider)?.get(modelId);
14
+ }
15
+ export function getModels(provider) {
16
+ if (provider) {
17
+ return Array.from(modelsByProvider.get(provider)?.values() ?? []);
18
+ }
19
+ return Array.from(modelsByProvider.values()).flatMap((entries) => Array.from(entries.values()));
20
+ }
21
+ export function getProviders() {
22
+ return Array.from(modelsByProvider.keys());
23
+ }
24
+ export function clearModels() {
25
+ modelsByProvider.clear();
26
+ }
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,20 @@
1
+ const providersByApi = new Map();
2
+ export function registerProvider(adapter, sourceId) {
3
+ providersByApi.set(adapter.api, { adapter, sourceId });
4
+ }
5
+ export function getProvider(api) {
6
+ return providersByApi.get(api)?.adapter;
7
+ }
8
+ export function getProviders() {
9
+ return Array.from(providersByApi.values(), (entry) => entry.adapter);
10
+ }
11
+ export function unregisterProviders(sourceId) {
12
+ for (const [api, entry] of providersByApi.entries()) {
13
+ if (entry.sourceId === sourceId) {
14
+ providersByApi.delete(api);
15
+ }
16
+ }
17
+ }
18
+ export function clearProviders() {
19
+ providersByApi.clear();
20
+ }