@agentic-kit/chat 1.11.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/LICENSE +23 -0
- package/README.md +193 -0
- package/esm/index.js +183 -0
- package/esm/model-registry.js +26 -0
- package/esm/provider-registry.js +20 -0
- package/esm/transform-messages.js +139 -0
- package/index.d.ts +34 -0
- package/index.js +222 -0
- package/model-registry.d.ts +7 -0
- package/model-registry.js +34 -0
- package/package.json +39 -0
- package/provider-registry.d.ts +6 -0
- package/provider-registry.js +27 -0
- package/transform-messages.d.ts +2 -0
- package/transform-messages.js +142 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dan Lynch <pyramation@gmail.com>
|
|
4
|
+
Copyright (c) 2025 Constructive <developers@constructive.io>
|
|
5
|
+
Copyright (c) 2020-present, Interweb, Inc.
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# @agentic-kit/chat
|
|
2
|
+
|
|
3
|
+
<p align="center" width="100%">
|
|
4
|
+
<img height="250" src="https://raw.githubusercontent.com/constructive-io/constructive/refs/heads/main/assets/outline-logo.svg" />
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center" width="100%">
|
|
8
|
+
<a href="https://github.com/constructive-io/agentic-kit/actions/workflows/run-tests.yaml">
|
|
9
|
+
<img height="20" src="https://github.com/constructive-io/agentic-kit/actions/workflows/run-tests.yaml/badge.svg" />
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://github.com/constructive-io/agentic-kit/blob/main/LICENSE"><img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/></a>
|
|
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
|
+
</p>
|
|
14
|
+
|
|
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
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install agentic-kit
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### Structured API
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { complete, getModel } from 'agentic-kit';
|
|
37
|
+
|
|
38
|
+
const model = getModel('openai', 'gpt-4o-mini');
|
|
39
|
+
const message = await complete(model!, {
|
|
40
|
+
messages: [{ role: 'user', content: 'Hello', timestamp: Date.now() }],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log(message.content);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Streaming
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
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
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## API Reference
|
|
64
|
+
|
|
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.
|
|
82
|
+
|
|
83
|
+
### Message helpers
|
|
84
|
+
|
|
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:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
interface GenerateInput {
|
|
112
|
+
model: string;
|
|
113
|
+
prompt?: string;
|
|
114
|
+
messages?: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>;
|
|
115
|
+
stream?: boolean;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Use `createOpenAIKit`, `createAnthropicKit`, `createOllamaKit`, or
|
|
120
|
+
`createMultiProviderKit()` if you still need the old prompt-only entrypoint.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Education and Tutorials
|
|
125
|
+
|
|
126
|
+
1. 🚀 [Quickstart: Getting Up and Running](https://constructive.io/learn/quickstart)
|
|
127
|
+
Get started with modular databases in minutes. Install prerequisites and deploy your first module.
|
|
128
|
+
|
|
129
|
+
2. 📦 [Modular PostgreSQL Development with Database Packages](https://constructive.io/learn/modular-postgres)
|
|
130
|
+
Learn to organize PostgreSQL projects with pgpm workspaces and reusable database modules.
|
|
131
|
+
|
|
132
|
+
3. ✏️ [Authoring Database Changes](https://constructive.io/learn/authoring-database-changes)
|
|
133
|
+
Master the workflow for adding, organizing, and managing database changes with pgpm.
|
|
134
|
+
|
|
135
|
+
4. 🧪 [End-to-End PostgreSQL Testing with TypeScript](https://constructive.io/learn/e2e-postgres-testing)
|
|
136
|
+
Master end-to-end PostgreSQL testing with ephemeral databases, RLS testing, and CI/CD automation.
|
|
137
|
+
|
|
138
|
+
5. ⚡ [Supabase Testing](https://constructive.io/learn/supabase)
|
|
139
|
+
Use TypeScript-first tools to test Supabase projects with realistic RLS, policies, and auth contexts.
|
|
140
|
+
|
|
141
|
+
6. 💧 [Drizzle ORM Testing](https://constructive.io/learn/drizzle-testing)
|
|
142
|
+
Run full-stack tests with Drizzle ORM, including database setup, teardown, and RLS enforcement.
|
|
143
|
+
|
|
144
|
+
7. 🔧 [Troubleshooting](https://constructive.io/learn/troubleshooting)
|
|
145
|
+
Common issues and solutions for pgpm, PostgreSQL, and testing.
|
|
146
|
+
|
|
147
|
+
## Related Constructive Tooling
|
|
148
|
+
|
|
149
|
+
### 📦 Package Management
|
|
150
|
+
|
|
151
|
+
* [pgpm](https://github.com/constructive-io/constructive/tree/main/pgpm/pgpm): **🖥️ PostgreSQL Package Manager** for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages.
|
|
152
|
+
|
|
153
|
+
### 🧪 Testing
|
|
154
|
+
|
|
155
|
+
* [pgsql-test](https://github.com/constructive-io/constructive/tree/main/postgres/pgsql-test): **📊 Isolated testing environments** with per-test transaction rollbacks—ideal for integration tests, complex migrations, and RLS simulation.
|
|
156
|
+
* [pgsql-seed](https://github.com/constructive-io/constructive/tree/main/postgres/pgsql-seed): **🌱 PostgreSQL seeding utilities** for CSV, JSON, SQL data loading, and pgpm deployment.
|
|
157
|
+
* [supabase-test](https://github.com/constructive-io/constructive/tree/main/postgres/supabase-test): **🧪 Supabase-native test harness** preconfigured for the local Supabase stack—per-test rollbacks, JWT/role context helpers, and CI/GitHub Actions ready.
|
|
158
|
+
* [graphile-test](https://github.com/constructive-io/constructive/tree/main/graphile/graphile-test): **🔐 Authentication mocking** for Graphile-focused test helpers and emulating row-level security contexts.
|
|
159
|
+
* [pg-query-context](https://github.com/constructive-io/constructive/tree/main/postgres/pg-query-context): **🔒 Session context injection** to add session-local context (e.g., `SET LOCAL`) into queries—ideal for setting `role`, `jwt.claims`, and other session settings.
|
|
160
|
+
|
|
161
|
+
### 🧠 Parsing & AST
|
|
162
|
+
|
|
163
|
+
* [pgsql-parser](https://www.npmjs.com/package/pgsql-parser): **🔄 SQL conversion engine** that interprets and converts PostgreSQL syntax.
|
|
164
|
+
* [libpg-query-node](https://www.npmjs.com/package/libpg-query): **🌉 Node.js bindings** for `libpg_query`, converting SQL into parse trees.
|
|
165
|
+
* [pg-proto-parser](https://www.npmjs.com/package/pg-proto-parser): **📦 Protobuf parser** for parsing PostgreSQL Protocol Buffers definitions to generate TypeScript interfaces, utility functions, and JSON mappings for enums.
|
|
166
|
+
* [@pgsql/enums](https://www.npmjs.com/package/@pgsql/enums): **🏷️ TypeScript enums** for PostgreSQL AST for safe and ergonomic parsing logic.
|
|
167
|
+
* [@pgsql/types](https://www.npmjs.com/package/@pgsql/types): **📝 Type definitions** for PostgreSQL AST nodes in TypeScript.
|
|
168
|
+
* [@pgsql/utils](https://www.npmjs.com/package/@pgsql/utils): **🛠️ AST utilities** for constructing and transforming PostgreSQL syntax trees.
|
|
169
|
+
|
|
170
|
+
### 📚 Documentation & Skills
|
|
171
|
+
|
|
172
|
+
* [constructive-skills](https://github.com/constructive-io/constructive-skills): **📖 Platform documentation and AI agent skills** — feature catalog, blueprint reference, SDK guides (i18n, billing, limits, events, uploads, security, entities, search, AI), and deployment guides.
|
|
173
|
+
|
|
174
|
+
Install skills for AI coding agents:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# All platform skills (security, blueprints, codegen, billing, etc.)
|
|
178
|
+
npx skills add constructive-io/constructive-skills
|
|
179
|
+
|
|
180
|
+
# Individual repo skills (pgpm, testing, CLI, search, etc.)
|
|
181
|
+
npx skills add https://github.com/constructive-io/constructive --skill pgpm
|
|
182
|
+
npx skills add https://github.com/constructive-io/constructive --skill constructive-testing
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Credits
|
|
186
|
+
|
|
187
|
+
**🛠 Built by the [Constructive](https://constructive.io) team — creators of modular Postgres tooling for secure, composable backends. If you like our work, contribute on [GitHub](https://github.com/constructive-io).**
|
|
188
|
+
|
|
189
|
+
## Disclaimer
|
|
190
|
+
|
|
191
|
+
AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
|
|
192
|
+
|
|
193
|
+
No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.
|
package/esm/index.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
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, createEmptyUsage, EventStream, getMessageText, normalizeContext, } from '@agentic-kit/protocol';
|
|
5
|
+
import { clearModels, getModel, getModels, getProviders as getModelProviders, registerModel, registerModels, } from './model-registry.js';
|
|
6
|
+
import { clearProviders, getProvider as getRegisteredProvider, getProviders as getRegisteredProviders, registerProvider, unregisterProviders, } from './provider-registry.js';
|
|
7
|
+
import { transformMessages } from './transform-messages.js';
|
|
8
|
+
export * from './transform-messages.js';
|
|
9
|
+
export * from '@agentic-kit/protocol';
|
|
10
|
+
export { createAssistantMessageEventStream, EventStream, OllamaClient };
|
|
11
|
+
export { AnthropicAdapter, OllamaAdapter, OpenAIAdapter };
|
|
12
|
+
registerModels([...OPENAI_COMPATIBLE_MODELS, ...ANTHROPIC_MODELS, ...OLLAMA_MODELS]);
|
|
13
|
+
registerProvider(new OpenAIAdapter());
|
|
14
|
+
registerProvider(new AnthropicAdapter({ apiKey: '' }));
|
|
15
|
+
registerProvider(new OllamaAdapter());
|
|
16
|
+
export function stream(model, context, options) {
|
|
17
|
+
const provider = getRegisteredProvider(model.api);
|
|
18
|
+
if (!provider) {
|
|
19
|
+
throw new Error(`No provider registered for api '${model.api}'`);
|
|
20
|
+
}
|
|
21
|
+
return streamWithProvider(provider, model, context, options);
|
|
22
|
+
}
|
|
23
|
+
export async function complete(model, context, options) {
|
|
24
|
+
const response = stream(model, context, options);
|
|
25
|
+
return response.result();
|
|
26
|
+
}
|
|
27
|
+
export async function completeText(model, context, options) {
|
|
28
|
+
const message = await complete(model, context, options);
|
|
29
|
+
return getMessageText(message);
|
|
30
|
+
}
|
|
31
|
+
export class AgentKit {
|
|
32
|
+
providers = new Map();
|
|
33
|
+
current;
|
|
34
|
+
addProvider(provider) {
|
|
35
|
+
this.providers.set(getProviderName(provider), provider);
|
|
36
|
+
if (!this.current) {
|
|
37
|
+
this.current = provider;
|
|
38
|
+
}
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
setProvider(name) {
|
|
42
|
+
const provider = this.providers.get(name);
|
|
43
|
+
if (!provider) {
|
|
44
|
+
throw new Error(`Provider '${name}' not found`);
|
|
45
|
+
}
|
|
46
|
+
this.current = provider;
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
getCurrentProvider() {
|
|
50
|
+
return this.current;
|
|
51
|
+
}
|
|
52
|
+
listProviders() {
|
|
53
|
+
return Array.from(this.providers.keys());
|
|
54
|
+
}
|
|
55
|
+
async generate(input, options) {
|
|
56
|
+
if (!this.current) {
|
|
57
|
+
throw new Error('No provider set. Call addProvider() first.');
|
|
58
|
+
}
|
|
59
|
+
const provider = this.current;
|
|
60
|
+
const model = createLegacyModel(provider, input.model, input.maxTokens);
|
|
61
|
+
const context = legacyInputToContext(input);
|
|
62
|
+
const streamOptions = {
|
|
63
|
+
maxTokens: input.maxTokens,
|
|
64
|
+
temperature: input.temperature,
|
|
65
|
+
};
|
|
66
|
+
if (options?.onChunk || input.stream) {
|
|
67
|
+
options?.onStateChange?.('streaming');
|
|
68
|
+
try {
|
|
69
|
+
const response = streamWithProvider(provider, model, context, streamOptions);
|
|
70
|
+
for await (const event of response) {
|
|
71
|
+
if (event.type === 'text_delta') {
|
|
72
|
+
options?.onChunk?.(event.delta);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
assertLegacyGenerateSucceeded(await response.result());
|
|
76
|
+
options?.onComplete?.();
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
options?.onError?.(error);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const message = assertLegacyGenerateSucceeded(await completeWithProvider(provider, model, context, streamOptions));
|
|
86
|
+
options?.onStateChange?.('complete');
|
|
87
|
+
const text = getMessageText(message);
|
|
88
|
+
options?.onComplete?.();
|
|
89
|
+
return text;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
options?.onError?.(error);
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async listModels() {
|
|
97
|
+
if (!this.current?.listModels) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
const models = await this.current.listModels();
|
|
101
|
+
return models.map((model) => (typeof model === 'string' ? model : model.id));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function createOllamaKit(baseUrl) {
|
|
105
|
+
return new AgentKit().addProvider(new OllamaAdapter(baseUrl));
|
|
106
|
+
}
|
|
107
|
+
export function createAnthropicKit(options) {
|
|
108
|
+
return new AgentKit().addProvider(new AnthropicAdapter(options));
|
|
109
|
+
}
|
|
110
|
+
export function createOpenAIKit(options) {
|
|
111
|
+
return new AgentKit().addProvider(new OpenAIAdapter(options));
|
|
112
|
+
}
|
|
113
|
+
export function createMultiProviderKit() {
|
|
114
|
+
return new AgentKit();
|
|
115
|
+
}
|
|
116
|
+
export { clearModels, clearProviders, getModel, getModelProviders, getModels, getRegisteredProvider as getProvider, getRegisteredProviders, registerModel, registerModels, registerProvider, unregisterProviders, };
|
|
117
|
+
function legacyInputToContext(input) {
|
|
118
|
+
const messages = input.messages
|
|
119
|
+
? input.messages
|
|
120
|
+
.filter((message) => message.role !== 'system')
|
|
121
|
+
.map((message) => message.role === 'assistant'
|
|
122
|
+
? {
|
|
123
|
+
role: 'assistant',
|
|
124
|
+
api: 'legacy',
|
|
125
|
+
provider: 'legacy',
|
|
126
|
+
model: input.model,
|
|
127
|
+
content: [{ type: 'text', text: message.content }],
|
|
128
|
+
usage: createEmptyUsage(),
|
|
129
|
+
stopReason: 'stop',
|
|
130
|
+
timestamp: Date.now(),
|
|
131
|
+
}
|
|
132
|
+
: {
|
|
133
|
+
role: 'user',
|
|
134
|
+
content: message.content,
|
|
135
|
+
timestamp: Date.now(),
|
|
136
|
+
})
|
|
137
|
+
: [{ role: 'user', content: input.prompt ?? '', timestamp: Date.now() }];
|
|
138
|
+
const systemPrompt = input.system ?? input.messages?.find((message) => message.role === 'system')?.content;
|
|
139
|
+
return {
|
|
140
|
+
systemPrompt,
|
|
141
|
+
messages,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function createLegacyModel(provider, modelId, maxTokens) {
|
|
145
|
+
if (provider.createModel) {
|
|
146
|
+
return provider.createModel(modelId, {
|
|
147
|
+
maxOutputTokens: maxTokens,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
id: modelId,
|
|
152
|
+
name: modelId,
|
|
153
|
+
api: provider.api,
|
|
154
|
+
provider: getProviderName(provider),
|
|
155
|
+
baseUrl: '',
|
|
156
|
+
input: ['text'],
|
|
157
|
+
reasoning: false,
|
|
158
|
+
tools: true,
|
|
159
|
+
maxOutputTokens: maxTokens,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function streamWithProvider(provider, model, context, options) {
|
|
163
|
+
const normalized = normalizeContext(context);
|
|
164
|
+
const transformedContext = {
|
|
165
|
+
...normalized,
|
|
166
|
+
messages: transformMessages(normalized.messages, model),
|
|
167
|
+
};
|
|
168
|
+
return provider.stream(model, transformedContext, options);
|
|
169
|
+
}
|
|
170
|
+
async function completeWithProvider(provider, model, context, options) {
|
|
171
|
+
const response = streamWithProvider(provider, model, context, options);
|
|
172
|
+
return response.result();
|
|
173
|
+
}
|
|
174
|
+
function getProviderName(provider) {
|
|
175
|
+
return provider.name ?? provider.provider;
|
|
176
|
+
}
|
|
177
|
+
function assertLegacyGenerateSucceeded(message) {
|
|
178
|
+
if (message.stopReason === 'error' || message.stopReason === 'aborted') {
|
|
179
|
+
throw new Error(message.errorMessage ??
|
|
180
|
+
(message.stopReason === 'aborted' ? 'Generation aborted.' : 'Generation failed.'), { cause: message });
|
|
181
|
+
}
|
|
182
|
+
return message;
|
|
183
|
+
}
|
|
@@ -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,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
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
export function transformMessages(messages, model) {
|
|
2
|
+
const toolCallIdMap = new Map();
|
|
3
|
+
const transformed = messages.flatMap((message) => {
|
|
4
|
+
if (message.role !== 'assistant') {
|
|
5
|
+
if (message.role === 'toolResult') {
|
|
6
|
+
const normalizedId = toolCallIdMap.get(message.toolCallId);
|
|
7
|
+
if (normalizedId && normalizedId !== message.toolCallId) {
|
|
8
|
+
return [{ ...message, toolCallId: normalizedId }];
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return [message];
|
|
12
|
+
}
|
|
13
|
+
if (message.stopReason === 'error' || message.stopReason === 'aborted') {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const sameModel = message.provider === model.provider &&
|
|
17
|
+
message.api === model.api &&
|
|
18
|
+
message.model === model.id;
|
|
19
|
+
const content = message.content.flatMap((block) => {
|
|
20
|
+
if (block.type === 'thinking') {
|
|
21
|
+
if (sameModel) {
|
|
22
|
+
return [block];
|
|
23
|
+
}
|
|
24
|
+
if (!block.thinking.trim()) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
const thinkingAsText = {
|
|
28
|
+
type: 'text',
|
|
29
|
+
text: `<thinking>${block.thinking}</thinking>`,
|
|
30
|
+
};
|
|
31
|
+
return [thinkingAsText];
|
|
32
|
+
}
|
|
33
|
+
if (block.type === 'toolCall') {
|
|
34
|
+
const normalizedId = normalizeToolCallId(block.id, model);
|
|
35
|
+
if (normalizedId !== block.id) {
|
|
36
|
+
toolCallIdMap.set(block.id, normalizedId);
|
|
37
|
+
}
|
|
38
|
+
const toolCall = normalizedId === block.id ? block : { ...block, id: normalizedId };
|
|
39
|
+
return [toolCall];
|
|
40
|
+
}
|
|
41
|
+
return [block];
|
|
42
|
+
});
|
|
43
|
+
return [{ ...message, content }];
|
|
44
|
+
});
|
|
45
|
+
return insertSyntheticToolResults(transformed);
|
|
46
|
+
}
|
|
47
|
+
function insertSyntheticToolResults(messages) {
|
|
48
|
+
const result = [];
|
|
49
|
+
let pendingToolCalls = [];
|
|
50
|
+
let seenToolResults = new Set();
|
|
51
|
+
for (const message of messages) {
|
|
52
|
+
if (message.role === 'assistant') {
|
|
53
|
+
flushPendingToolCalls(result, pendingToolCalls, seenToolResults);
|
|
54
|
+
pendingToolCalls = message.content.filter((block) => block.type === 'toolCall');
|
|
55
|
+
seenToolResults = new Set();
|
|
56
|
+
result.push(message);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (message.role === 'toolResult') {
|
|
60
|
+
seenToolResults.add(message.toolCallId);
|
|
61
|
+
result.push(message);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
flushPendingToolCalls(result, pendingToolCalls, seenToolResults);
|
|
65
|
+
pendingToolCalls = [];
|
|
66
|
+
seenToolResults = new Set();
|
|
67
|
+
result.push(message);
|
|
68
|
+
}
|
|
69
|
+
flushPendingToolCalls(result, pendingToolCalls, seenToolResults);
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
function flushPendingToolCalls(result, pendingToolCalls, seenToolResults) {
|
|
73
|
+
if (pendingToolCalls.length === 0) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
for (const toolCall of pendingToolCalls) {
|
|
77
|
+
if (seenToolResults.has(toolCall.id)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const synthetic = {
|
|
81
|
+
role: 'toolResult',
|
|
82
|
+
toolCallId: toolCall.id,
|
|
83
|
+
toolName: toolCall.name,
|
|
84
|
+
content: [{ type: 'text', text: 'No result provided.' }],
|
|
85
|
+
isError: true,
|
|
86
|
+
timestamp: Date.now(),
|
|
87
|
+
};
|
|
88
|
+
result.push(synthetic);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function normalizeToolCallId(id, model) {
|
|
92
|
+
const format = model.compat?.toolCallIdFormat ?? defaultToolCallIdFormat(model);
|
|
93
|
+
if (format === 'passthrough') {
|
|
94
|
+
return id;
|
|
95
|
+
}
|
|
96
|
+
if (format === 'mistral9') {
|
|
97
|
+
const alphanumeric = id.replace(/[^a-zA-Z0-9]/g, '');
|
|
98
|
+
if (alphanumeric.length >= 9) {
|
|
99
|
+
return alphanumeric.slice(0, 9);
|
|
100
|
+
}
|
|
101
|
+
return stableAlphanumericId(id, 9);
|
|
102
|
+
}
|
|
103
|
+
const safe = id.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64);
|
|
104
|
+
if (safe.length > 0) {
|
|
105
|
+
return safe;
|
|
106
|
+
}
|
|
107
|
+
return stableId(id, 24, /^[a-zA-Z0-9_-]+$/);
|
|
108
|
+
}
|
|
109
|
+
function defaultToolCallIdFormat(model) {
|
|
110
|
+
if (model.provider === 'anthropic' || model.api === 'anthropic-messages') {
|
|
111
|
+
return 'safe64';
|
|
112
|
+
}
|
|
113
|
+
return 'passthrough';
|
|
114
|
+
}
|
|
115
|
+
function stableId(input, length, pattern) {
|
|
116
|
+
let hash = 0;
|
|
117
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
118
|
+
hash = (hash * 31 + input.charCodeAt(i)) >>> 0;
|
|
119
|
+
}
|
|
120
|
+
let value = `tc_${hash.toString(36)}`;
|
|
121
|
+
while (!pattern.test(value)) {
|
|
122
|
+
value = value.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
123
|
+
}
|
|
124
|
+
if (value.length >= length) {
|
|
125
|
+
return value.slice(0, length);
|
|
126
|
+
}
|
|
127
|
+
return value.padEnd(length, '0');
|
|
128
|
+
}
|
|
129
|
+
function stableAlphanumericId(input, length) {
|
|
130
|
+
let hash = 0;
|
|
131
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
132
|
+
hash = (hash * 31 + input.charCodeAt(i)) >>> 0;
|
|
133
|
+
}
|
|
134
|
+
const value = `tc${hash.toString(36)}`.replace(/[^a-zA-Z0-9]/g, '');
|
|
135
|
+
if (value.length >= length) {
|
|
136
|
+
return value.slice(0, length);
|
|
137
|
+
}
|
|
138
|
+
return value.padEnd(length, '0');
|
|
139
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AnthropicAdapter, type AnthropicOptions } from '@agentic-kit/anthropic';
|
|
2
|
+
import { OllamaAdapter, OllamaClient } from '@agentic-kit/ollama';
|
|
3
|
+
import { OpenAIAdapter, type OpenAIOptions } from '@agentic-kit/openai';
|
|
4
|
+
import { type AssistantMessage, type AssistantMessageEventStream, type Context, createAssistantMessageEventStream, EventStream, type LegacyChatMessage, type LegacyGenerateInput, type LegacyStreamingOptions, type ModelDescriptor, type ProviderAdapter, type StreamOptions } from '@agentic-kit/protocol';
|
|
5
|
+
import { clearModels, getModel, getModels, getProviders as getModelProviders, registerModel, registerModels } from './model-registry.js';
|
|
6
|
+
import { clearProviders, getProvider as getRegisteredProvider, getProviders as getRegisteredProviders, registerProvider, unregisterProviders } from './provider-registry.js';
|
|
7
|
+
export * from './transform-messages.js';
|
|
8
|
+
export * from '@agentic-kit/protocol';
|
|
9
|
+
export { createAssistantMessageEventStream, EventStream, OllamaClient };
|
|
10
|
+
export { AnthropicAdapter, OllamaAdapter, OpenAIAdapter };
|
|
11
|
+
export type { AnthropicOptions, OpenAIOptions };
|
|
12
|
+
export type ChatMessage = LegacyChatMessage;
|
|
13
|
+
export type GenerateInput = LegacyGenerateInput;
|
|
14
|
+
type NamedProviderAdapter = ProviderAdapter & {
|
|
15
|
+
name?: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function stream(model: ModelDescriptor, context: Context, options?: StreamOptions): AssistantMessageEventStream;
|
|
18
|
+
export declare function complete(model: ModelDescriptor, context: Context, options?: StreamOptions): Promise<AssistantMessage>;
|
|
19
|
+
export declare function completeText(model: ModelDescriptor, context: Context, options?: StreamOptions): Promise<string>;
|
|
20
|
+
export declare class AgentKit {
|
|
21
|
+
private readonly providers;
|
|
22
|
+
private current?;
|
|
23
|
+
addProvider(provider: NamedProviderAdapter): this;
|
|
24
|
+
setProvider(name: string): this;
|
|
25
|
+
getCurrentProvider(): NamedProviderAdapter | undefined;
|
|
26
|
+
listProviders(): string[];
|
|
27
|
+
generate(input: LegacyGenerateInput, options?: LegacyStreamingOptions): Promise<string | void>;
|
|
28
|
+
listModels(): Promise<string[]>;
|
|
29
|
+
}
|
|
30
|
+
export declare function createOllamaKit(baseUrl?: string): AgentKit;
|
|
31
|
+
export declare function createAnthropicKit(options: AnthropicOptions | string): AgentKit;
|
|
32
|
+
export declare function createOpenAIKit(options?: OpenAIOptions | string): AgentKit;
|
|
33
|
+
export declare function createMultiProviderKit(): AgentKit;
|
|
34
|
+
export { clearModels, clearProviders, getModel, getModelProviders, getModels, getRegisteredProvider as getProvider, getRegisteredProviders, registerModel, registerModels, registerProvider, unregisterProviders, };
|
package/index.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.unregisterProviders = exports.registerProvider = exports.registerModels = exports.registerModel = exports.getRegisteredProviders = exports.getProvider = exports.getModels = exports.getModelProviders = exports.getModel = exports.clearProviders = exports.clearModels = exports.AgentKit = exports.OpenAIAdapter = exports.OllamaAdapter = exports.AnthropicAdapter = exports.OllamaClient = exports.EventStream = exports.createAssistantMessageEventStream = void 0;
|
|
18
|
+
exports.stream = stream;
|
|
19
|
+
exports.complete = complete;
|
|
20
|
+
exports.completeText = completeText;
|
|
21
|
+
exports.createOllamaKit = createOllamaKit;
|
|
22
|
+
exports.createAnthropicKit = createAnthropicKit;
|
|
23
|
+
exports.createOpenAIKit = createOpenAIKit;
|
|
24
|
+
exports.createMultiProviderKit = createMultiProviderKit;
|
|
25
|
+
const anthropic_1 = require("@agentic-kit/anthropic");
|
|
26
|
+
Object.defineProperty(exports, "AnthropicAdapter", { enumerable: true, get: function () { return anthropic_1.AnthropicAdapter; } });
|
|
27
|
+
const ollama_1 = require("@agentic-kit/ollama");
|
|
28
|
+
Object.defineProperty(exports, "OllamaAdapter", { enumerable: true, get: function () { return ollama_1.OllamaAdapter; } });
|
|
29
|
+
Object.defineProperty(exports, "OllamaClient", { enumerable: true, get: function () { return ollama_1.OllamaClient; } });
|
|
30
|
+
const openai_1 = require("@agentic-kit/openai");
|
|
31
|
+
Object.defineProperty(exports, "OpenAIAdapter", { enumerable: true, get: function () { return openai_1.OpenAIAdapter; } });
|
|
32
|
+
const protocol_1 = require("@agentic-kit/protocol");
|
|
33
|
+
Object.defineProperty(exports, "createAssistantMessageEventStream", { enumerable: true, get: function () { return protocol_1.createAssistantMessageEventStream; } });
|
|
34
|
+
Object.defineProperty(exports, "EventStream", { enumerable: true, get: function () { return protocol_1.EventStream; } });
|
|
35
|
+
const model_registry_js_1 = require("./model-registry.js");
|
|
36
|
+
Object.defineProperty(exports, "clearModels", { enumerable: true, get: function () { return model_registry_js_1.clearModels; } });
|
|
37
|
+
Object.defineProperty(exports, "getModel", { enumerable: true, get: function () { return model_registry_js_1.getModel; } });
|
|
38
|
+
Object.defineProperty(exports, "getModels", { enumerable: true, get: function () { return model_registry_js_1.getModels; } });
|
|
39
|
+
Object.defineProperty(exports, "getModelProviders", { enumerable: true, get: function () { return model_registry_js_1.getProviders; } });
|
|
40
|
+
Object.defineProperty(exports, "registerModel", { enumerable: true, get: function () { return model_registry_js_1.registerModel; } });
|
|
41
|
+
Object.defineProperty(exports, "registerModels", { enumerable: true, get: function () { return model_registry_js_1.registerModels; } });
|
|
42
|
+
const provider_registry_js_1 = require("./provider-registry.js");
|
|
43
|
+
Object.defineProperty(exports, "clearProviders", { enumerable: true, get: function () { return provider_registry_js_1.clearProviders; } });
|
|
44
|
+
Object.defineProperty(exports, "getProvider", { enumerable: true, get: function () { return provider_registry_js_1.getProvider; } });
|
|
45
|
+
Object.defineProperty(exports, "getRegisteredProviders", { enumerable: true, get: function () { return provider_registry_js_1.getProviders; } });
|
|
46
|
+
Object.defineProperty(exports, "registerProvider", { enumerable: true, get: function () { return provider_registry_js_1.registerProvider; } });
|
|
47
|
+
Object.defineProperty(exports, "unregisterProviders", { enumerable: true, get: function () { return provider_registry_js_1.unregisterProviders; } });
|
|
48
|
+
const transform_messages_js_1 = require("./transform-messages.js");
|
|
49
|
+
__exportStar(require("./transform-messages.js"), exports);
|
|
50
|
+
__exportStar(require("@agentic-kit/protocol"), exports);
|
|
51
|
+
(0, model_registry_js_1.registerModels)([...openai_1.OPENAI_COMPATIBLE_MODELS, ...anthropic_1.ANTHROPIC_MODELS, ...ollama_1.OLLAMA_MODELS]);
|
|
52
|
+
(0, provider_registry_js_1.registerProvider)(new openai_1.OpenAIAdapter());
|
|
53
|
+
(0, provider_registry_js_1.registerProvider)(new anthropic_1.AnthropicAdapter({ apiKey: '' }));
|
|
54
|
+
(0, provider_registry_js_1.registerProvider)(new ollama_1.OllamaAdapter());
|
|
55
|
+
function stream(model, context, options) {
|
|
56
|
+
const provider = (0, provider_registry_js_1.getProvider)(model.api);
|
|
57
|
+
if (!provider) {
|
|
58
|
+
throw new Error(`No provider registered for api '${model.api}'`);
|
|
59
|
+
}
|
|
60
|
+
return streamWithProvider(provider, model, context, options);
|
|
61
|
+
}
|
|
62
|
+
async function complete(model, context, options) {
|
|
63
|
+
const response = stream(model, context, options);
|
|
64
|
+
return response.result();
|
|
65
|
+
}
|
|
66
|
+
async function completeText(model, context, options) {
|
|
67
|
+
const message = await complete(model, context, options);
|
|
68
|
+
return (0, protocol_1.getMessageText)(message);
|
|
69
|
+
}
|
|
70
|
+
class AgentKit {
|
|
71
|
+
providers = new Map();
|
|
72
|
+
current;
|
|
73
|
+
addProvider(provider) {
|
|
74
|
+
this.providers.set(getProviderName(provider), provider);
|
|
75
|
+
if (!this.current) {
|
|
76
|
+
this.current = provider;
|
|
77
|
+
}
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
setProvider(name) {
|
|
81
|
+
const provider = this.providers.get(name);
|
|
82
|
+
if (!provider) {
|
|
83
|
+
throw new Error(`Provider '${name}' not found`);
|
|
84
|
+
}
|
|
85
|
+
this.current = provider;
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
getCurrentProvider() {
|
|
89
|
+
return this.current;
|
|
90
|
+
}
|
|
91
|
+
listProviders() {
|
|
92
|
+
return Array.from(this.providers.keys());
|
|
93
|
+
}
|
|
94
|
+
async generate(input, options) {
|
|
95
|
+
if (!this.current) {
|
|
96
|
+
throw new Error('No provider set. Call addProvider() first.');
|
|
97
|
+
}
|
|
98
|
+
const provider = this.current;
|
|
99
|
+
const model = createLegacyModel(provider, input.model, input.maxTokens);
|
|
100
|
+
const context = legacyInputToContext(input);
|
|
101
|
+
const streamOptions = {
|
|
102
|
+
maxTokens: input.maxTokens,
|
|
103
|
+
temperature: input.temperature,
|
|
104
|
+
};
|
|
105
|
+
if (options?.onChunk || input.stream) {
|
|
106
|
+
options?.onStateChange?.('streaming');
|
|
107
|
+
try {
|
|
108
|
+
const response = streamWithProvider(provider, model, context, streamOptions);
|
|
109
|
+
for await (const event of response) {
|
|
110
|
+
if (event.type === 'text_delta') {
|
|
111
|
+
options?.onChunk?.(event.delta);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
assertLegacyGenerateSucceeded(await response.result());
|
|
115
|
+
options?.onComplete?.();
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
options?.onError?.(error);
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const message = assertLegacyGenerateSucceeded(await completeWithProvider(provider, model, context, streamOptions));
|
|
125
|
+
options?.onStateChange?.('complete');
|
|
126
|
+
const text = (0, protocol_1.getMessageText)(message);
|
|
127
|
+
options?.onComplete?.();
|
|
128
|
+
return text;
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
options?.onError?.(error);
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async listModels() {
|
|
136
|
+
if (!this.current?.listModels) {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
const models = await this.current.listModels();
|
|
140
|
+
return models.map((model) => (typeof model === 'string' ? model : model.id));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
exports.AgentKit = AgentKit;
|
|
144
|
+
function createOllamaKit(baseUrl) {
|
|
145
|
+
return new AgentKit().addProvider(new ollama_1.OllamaAdapter(baseUrl));
|
|
146
|
+
}
|
|
147
|
+
function createAnthropicKit(options) {
|
|
148
|
+
return new AgentKit().addProvider(new anthropic_1.AnthropicAdapter(options));
|
|
149
|
+
}
|
|
150
|
+
function createOpenAIKit(options) {
|
|
151
|
+
return new AgentKit().addProvider(new openai_1.OpenAIAdapter(options));
|
|
152
|
+
}
|
|
153
|
+
function createMultiProviderKit() {
|
|
154
|
+
return new AgentKit();
|
|
155
|
+
}
|
|
156
|
+
function legacyInputToContext(input) {
|
|
157
|
+
const messages = input.messages
|
|
158
|
+
? input.messages
|
|
159
|
+
.filter((message) => message.role !== 'system')
|
|
160
|
+
.map((message) => message.role === 'assistant'
|
|
161
|
+
? {
|
|
162
|
+
role: 'assistant',
|
|
163
|
+
api: 'legacy',
|
|
164
|
+
provider: 'legacy',
|
|
165
|
+
model: input.model,
|
|
166
|
+
content: [{ type: 'text', text: message.content }],
|
|
167
|
+
usage: (0, protocol_1.createEmptyUsage)(),
|
|
168
|
+
stopReason: 'stop',
|
|
169
|
+
timestamp: Date.now(),
|
|
170
|
+
}
|
|
171
|
+
: {
|
|
172
|
+
role: 'user',
|
|
173
|
+
content: message.content,
|
|
174
|
+
timestamp: Date.now(),
|
|
175
|
+
})
|
|
176
|
+
: [{ role: 'user', content: input.prompt ?? '', timestamp: Date.now() }];
|
|
177
|
+
const systemPrompt = input.system ?? input.messages?.find((message) => message.role === 'system')?.content;
|
|
178
|
+
return {
|
|
179
|
+
systemPrompt,
|
|
180
|
+
messages,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function createLegacyModel(provider, modelId, maxTokens) {
|
|
184
|
+
if (provider.createModel) {
|
|
185
|
+
return provider.createModel(modelId, {
|
|
186
|
+
maxOutputTokens: maxTokens,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
id: modelId,
|
|
191
|
+
name: modelId,
|
|
192
|
+
api: provider.api,
|
|
193
|
+
provider: getProviderName(provider),
|
|
194
|
+
baseUrl: '',
|
|
195
|
+
input: ['text'],
|
|
196
|
+
reasoning: false,
|
|
197
|
+
tools: true,
|
|
198
|
+
maxOutputTokens: maxTokens,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function streamWithProvider(provider, model, context, options) {
|
|
202
|
+
const normalized = (0, protocol_1.normalizeContext)(context);
|
|
203
|
+
const transformedContext = {
|
|
204
|
+
...normalized,
|
|
205
|
+
messages: (0, transform_messages_js_1.transformMessages)(normalized.messages, model),
|
|
206
|
+
};
|
|
207
|
+
return provider.stream(model, transformedContext, options);
|
|
208
|
+
}
|
|
209
|
+
async function completeWithProvider(provider, model, context, options) {
|
|
210
|
+
const response = streamWithProvider(provider, model, context, options);
|
|
211
|
+
return response.result();
|
|
212
|
+
}
|
|
213
|
+
function getProviderName(provider) {
|
|
214
|
+
return provider.name ?? provider.provider;
|
|
215
|
+
}
|
|
216
|
+
function assertLegacyGenerateSucceeded(message) {
|
|
217
|
+
if (message.stopReason === 'error' || message.stopReason === 'aborted') {
|
|
218
|
+
throw new Error(message.errorMessage ??
|
|
219
|
+
(message.stopReason === 'aborted' ? 'Generation aborted.' : 'Generation failed.'), { cause: message });
|
|
220
|
+
}
|
|
221
|
+
return message;
|
|
222
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ModelDescriptor } from '@agentic-kit/protocol';
|
|
2
|
+
export declare function registerModel(model: ModelDescriptor): void;
|
|
3
|
+
export declare function registerModels(models: ModelDescriptor[]): void;
|
|
4
|
+
export declare function getModel(provider: string, modelId: string): ModelDescriptor | undefined;
|
|
5
|
+
export declare function getModels(provider?: string): ModelDescriptor[];
|
|
6
|
+
export declare function getProviders(): string[];
|
|
7
|
+
export declare function clearModels(): void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerModel = registerModel;
|
|
4
|
+
exports.registerModels = registerModels;
|
|
5
|
+
exports.getModel = getModel;
|
|
6
|
+
exports.getModels = getModels;
|
|
7
|
+
exports.getProviders = getProviders;
|
|
8
|
+
exports.clearModels = clearModels;
|
|
9
|
+
const modelsByProvider = new Map();
|
|
10
|
+
function registerModel(model) {
|
|
11
|
+
const providerModels = modelsByProvider.get(model.provider) ?? new Map();
|
|
12
|
+
providerModels.set(model.id, model);
|
|
13
|
+
modelsByProvider.set(model.provider, providerModels);
|
|
14
|
+
}
|
|
15
|
+
function registerModels(models) {
|
|
16
|
+
for (const model of models) {
|
|
17
|
+
registerModel(model);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function getModel(provider, modelId) {
|
|
21
|
+
return modelsByProvider.get(provider)?.get(modelId);
|
|
22
|
+
}
|
|
23
|
+
function getModels(provider) {
|
|
24
|
+
if (provider) {
|
|
25
|
+
return Array.from(modelsByProvider.get(provider)?.values() ?? []);
|
|
26
|
+
}
|
|
27
|
+
return Array.from(modelsByProvider.values()).flatMap((entries) => Array.from(entries.values()));
|
|
28
|
+
}
|
|
29
|
+
function getProviders() {
|
|
30
|
+
return Array.from(modelsByProvider.keys());
|
|
31
|
+
}
|
|
32
|
+
function clearModels() {
|
|
33
|
+
modelsByProvider.clear();
|
|
34
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentic-kit/chat",
|
|
3
|
+
"version": "1.11.0",
|
|
4
|
+
"author": "Dan Lynch <pyramation@gmail.com>",
|
|
5
|
+
"description": "Provider-agnostic LLM adapter with streaming and multi-turn support",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"module": "esm/index.js",
|
|
8
|
+
"types": "index.d.ts",
|
|
9
|
+
"homepage": "https://github.com/constructive-io/constructive",
|
|
10
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public",
|
|
13
|
+
"directory": "dist"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/constructive-io/constructive"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/constructive-io/constructive/issues"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"clean": "makage clean",
|
|
24
|
+
"prepack": "npm run build",
|
|
25
|
+
"build": "makage build",
|
|
26
|
+
"build:dev": "makage build --dev",
|
|
27
|
+
"lint": "eslint . --fix",
|
|
28
|
+
"test": "jest",
|
|
29
|
+
"test:watch": "jest --watch"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@agentic-kit/anthropic": "1.9.1",
|
|
33
|
+
"@agentic-kit/ollama": "2.7.1",
|
|
34
|
+
"@agentic-kit/openai": "1.9.1",
|
|
35
|
+
"@agentic-kit/protocol": "1.9.0"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [],
|
|
38
|
+
"gitHead": "31f7cd3d3cc28cf069056eae5b2a0d4198d154a1"
|
|
39
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ProviderAdapter } from '@agentic-kit/protocol';
|
|
2
|
+
export declare function registerProvider(adapter: ProviderAdapter, sourceId?: string): void;
|
|
3
|
+
export declare function getProvider(api: string): ProviderAdapter | undefined;
|
|
4
|
+
export declare function getProviders(): ProviderAdapter[];
|
|
5
|
+
export declare function unregisterProviders(sourceId: string): void;
|
|
6
|
+
export declare function clearProviders(): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerProvider = registerProvider;
|
|
4
|
+
exports.getProvider = getProvider;
|
|
5
|
+
exports.getProviders = getProviders;
|
|
6
|
+
exports.unregisterProviders = unregisterProviders;
|
|
7
|
+
exports.clearProviders = clearProviders;
|
|
8
|
+
const providersByApi = new Map();
|
|
9
|
+
function registerProvider(adapter, sourceId) {
|
|
10
|
+
providersByApi.set(adapter.api, { adapter, sourceId });
|
|
11
|
+
}
|
|
12
|
+
function getProvider(api) {
|
|
13
|
+
return providersByApi.get(api)?.adapter;
|
|
14
|
+
}
|
|
15
|
+
function getProviders() {
|
|
16
|
+
return Array.from(providersByApi.values(), (entry) => entry.adapter);
|
|
17
|
+
}
|
|
18
|
+
function unregisterProviders(sourceId) {
|
|
19
|
+
for (const [api, entry] of providersByApi.entries()) {
|
|
20
|
+
if (entry.sourceId === sourceId) {
|
|
21
|
+
providersByApi.delete(api);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function clearProviders() {
|
|
26
|
+
providersByApi.clear();
|
|
27
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.transformMessages = transformMessages;
|
|
4
|
+
function transformMessages(messages, model) {
|
|
5
|
+
const toolCallIdMap = new Map();
|
|
6
|
+
const transformed = messages.flatMap((message) => {
|
|
7
|
+
if (message.role !== 'assistant') {
|
|
8
|
+
if (message.role === 'toolResult') {
|
|
9
|
+
const normalizedId = toolCallIdMap.get(message.toolCallId);
|
|
10
|
+
if (normalizedId && normalizedId !== message.toolCallId) {
|
|
11
|
+
return [{ ...message, toolCallId: normalizedId }];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return [message];
|
|
15
|
+
}
|
|
16
|
+
if (message.stopReason === 'error' || message.stopReason === 'aborted') {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
const sameModel = message.provider === model.provider &&
|
|
20
|
+
message.api === model.api &&
|
|
21
|
+
message.model === model.id;
|
|
22
|
+
const content = message.content.flatMap((block) => {
|
|
23
|
+
if (block.type === 'thinking') {
|
|
24
|
+
if (sameModel) {
|
|
25
|
+
return [block];
|
|
26
|
+
}
|
|
27
|
+
if (!block.thinking.trim()) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
const thinkingAsText = {
|
|
31
|
+
type: 'text',
|
|
32
|
+
text: `<thinking>${block.thinking}</thinking>`,
|
|
33
|
+
};
|
|
34
|
+
return [thinkingAsText];
|
|
35
|
+
}
|
|
36
|
+
if (block.type === 'toolCall') {
|
|
37
|
+
const normalizedId = normalizeToolCallId(block.id, model);
|
|
38
|
+
if (normalizedId !== block.id) {
|
|
39
|
+
toolCallIdMap.set(block.id, normalizedId);
|
|
40
|
+
}
|
|
41
|
+
const toolCall = normalizedId === block.id ? block : { ...block, id: normalizedId };
|
|
42
|
+
return [toolCall];
|
|
43
|
+
}
|
|
44
|
+
return [block];
|
|
45
|
+
});
|
|
46
|
+
return [{ ...message, content }];
|
|
47
|
+
});
|
|
48
|
+
return insertSyntheticToolResults(transformed);
|
|
49
|
+
}
|
|
50
|
+
function insertSyntheticToolResults(messages) {
|
|
51
|
+
const result = [];
|
|
52
|
+
let pendingToolCalls = [];
|
|
53
|
+
let seenToolResults = new Set();
|
|
54
|
+
for (const message of messages) {
|
|
55
|
+
if (message.role === 'assistant') {
|
|
56
|
+
flushPendingToolCalls(result, pendingToolCalls, seenToolResults);
|
|
57
|
+
pendingToolCalls = message.content.filter((block) => block.type === 'toolCall');
|
|
58
|
+
seenToolResults = new Set();
|
|
59
|
+
result.push(message);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (message.role === 'toolResult') {
|
|
63
|
+
seenToolResults.add(message.toolCallId);
|
|
64
|
+
result.push(message);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
flushPendingToolCalls(result, pendingToolCalls, seenToolResults);
|
|
68
|
+
pendingToolCalls = [];
|
|
69
|
+
seenToolResults = new Set();
|
|
70
|
+
result.push(message);
|
|
71
|
+
}
|
|
72
|
+
flushPendingToolCalls(result, pendingToolCalls, seenToolResults);
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
function flushPendingToolCalls(result, pendingToolCalls, seenToolResults) {
|
|
76
|
+
if (pendingToolCalls.length === 0) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
for (const toolCall of pendingToolCalls) {
|
|
80
|
+
if (seenToolResults.has(toolCall.id)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const synthetic = {
|
|
84
|
+
role: 'toolResult',
|
|
85
|
+
toolCallId: toolCall.id,
|
|
86
|
+
toolName: toolCall.name,
|
|
87
|
+
content: [{ type: 'text', text: 'No result provided.' }],
|
|
88
|
+
isError: true,
|
|
89
|
+
timestamp: Date.now(),
|
|
90
|
+
};
|
|
91
|
+
result.push(synthetic);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function normalizeToolCallId(id, model) {
|
|
95
|
+
const format = model.compat?.toolCallIdFormat ?? defaultToolCallIdFormat(model);
|
|
96
|
+
if (format === 'passthrough') {
|
|
97
|
+
return id;
|
|
98
|
+
}
|
|
99
|
+
if (format === 'mistral9') {
|
|
100
|
+
const alphanumeric = id.replace(/[^a-zA-Z0-9]/g, '');
|
|
101
|
+
if (alphanumeric.length >= 9) {
|
|
102
|
+
return alphanumeric.slice(0, 9);
|
|
103
|
+
}
|
|
104
|
+
return stableAlphanumericId(id, 9);
|
|
105
|
+
}
|
|
106
|
+
const safe = id.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64);
|
|
107
|
+
if (safe.length > 0) {
|
|
108
|
+
return safe;
|
|
109
|
+
}
|
|
110
|
+
return stableId(id, 24, /^[a-zA-Z0-9_-]+$/);
|
|
111
|
+
}
|
|
112
|
+
function defaultToolCallIdFormat(model) {
|
|
113
|
+
if (model.provider === 'anthropic' || model.api === 'anthropic-messages') {
|
|
114
|
+
return 'safe64';
|
|
115
|
+
}
|
|
116
|
+
return 'passthrough';
|
|
117
|
+
}
|
|
118
|
+
function stableId(input, length, pattern) {
|
|
119
|
+
let hash = 0;
|
|
120
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
121
|
+
hash = (hash * 31 + input.charCodeAt(i)) >>> 0;
|
|
122
|
+
}
|
|
123
|
+
let value = `tc_${hash.toString(36)}`;
|
|
124
|
+
while (!pattern.test(value)) {
|
|
125
|
+
value = value.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
126
|
+
}
|
|
127
|
+
if (value.length >= length) {
|
|
128
|
+
return value.slice(0, length);
|
|
129
|
+
}
|
|
130
|
+
return value.padEnd(length, '0');
|
|
131
|
+
}
|
|
132
|
+
function stableAlphanumericId(input, length) {
|
|
133
|
+
let hash = 0;
|
|
134
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
135
|
+
hash = (hash * 31 + input.charCodeAt(i)) >>> 0;
|
|
136
|
+
}
|
|
137
|
+
const value = `tc${hash.toString(36)}`.replace(/[^a-zA-Z0-9]/g, '');
|
|
138
|
+
if (value.length >= length) {
|
|
139
|
+
return value.slice(0, length);
|
|
140
|
+
}
|
|
141
|
+
return value.padEnd(length, '0');
|
|
142
|
+
}
|