@hazeljs/ai 0.2.0-beta.8 → 0.2.0-beta.80
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 +192 -21
- package/README.md +7 -6
- package/dist/ai-enhanced.service.js +1 -1
- package/dist/ai-enhanced.service.test.d.ts +2 -0
- package/dist/ai-enhanced.service.test.d.ts.map +1 -0
- package/dist/ai-enhanced.service.test.js +501 -0
- package/dist/ai-enhanced.test.d.ts +2 -0
- package/dist/ai-enhanced.test.d.ts.map +1 -0
- package/dist/ai-enhanced.test.js +587 -0
- package/dist/ai-enhanced.types.d.ts +8 -0
- package/dist/ai-enhanced.types.d.ts.map +1 -1
- package/dist/ai.decorator.test.d.ts +2 -0
- package/dist/ai.decorator.test.d.ts.map +1 -0
- package/dist/ai.decorator.test.js +189 -0
- package/dist/ai.module.test.d.ts +2 -0
- package/dist/ai.module.test.d.ts.map +1 -0
- package/dist/ai.module.test.js +23 -0
- package/dist/ai.service.d.ts +1 -0
- package/dist/ai.service.d.ts.map +1 -1
- package/dist/ai.service.js +9 -4
- package/dist/ai.service.test.d.ts +2 -0
- package/dist/ai.service.test.d.ts.map +1 -0
- package/dist/ai.service.test.js +222 -0
- package/dist/context/context.manager.test.d.ts +2 -0
- package/dist/context/context.manager.test.d.ts.map +1 -0
- package/dist/context/context.manager.test.js +180 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/prompts/task.prompt.d.ts +12 -0
- package/dist/prompts/task.prompt.d.ts.map +1 -0
- package/dist/prompts/task.prompt.js +12 -0
- package/dist/providers/anthropic.provider.test.d.ts +2 -0
- package/dist/providers/anthropic.provider.test.d.ts.map +1 -0
- package/dist/providers/anthropic.provider.test.js +222 -0
- package/dist/providers/cohere.provider.test.d.ts +2 -0
- package/dist/providers/cohere.provider.test.d.ts.map +1 -0
- package/dist/providers/cohere.provider.test.js +267 -0
- package/dist/providers/gemini.provider.test.d.ts +2 -0
- package/dist/providers/gemini.provider.test.d.ts.map +1 -0
- package/dist/providers/gemini.provider.test.js +219 -0
- package/dist/providers/ollama.provider.test.d.ts +2 -0
- package/dist/providers/ollama.provider.test.d.ts.map +1 -0
- package/dist/providers/ollama.provider.test.js +267 -0
- package/dist/providers/openai.provider.d.ts +10 -0
- package/dist/providers/openai.provider.d.ts.map +1 -1
- package/dist/providers/openai.provider.js +38 -0
- package/dist/providers/openai.provider.test.d.ts +2 -0
- package/dist/providers/openai.provider.test.d.ts.map +1 -0
- package/dist/providers/openai.provider.test.js +364 -0
- package/dist/tracking/token.tracker.js +1 -1
- package/dist/tracking/token.tracker.test.d.ts +2 -0
- package/dist/tracking/token.tracker.test.d.ts.map +1 -0
- package/dist/tracking/token.tracker.test.js +272 -0
- package/dist/vector/vector.service.js +1 -1
- package/package.json +13 -6
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
jest.mock('@hazeljs/core', () => ({
|
|
4
|
+
__esModule: true,
|
|
5
|
+
default: { info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
|
|
6
|
+
}));
|
|
7
|
+
const context_manager_1 = require("./context.manager");
|
|
8
|
+
describe('AIContextManager', () => {
|
|
9
|
+
let manager;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
manager = new context_manager_1.AIContextManager(200);
|
|
12
|
+
});
|
|
13
|
+
describe('constructor', () => {
|
|
14
|
+
it('initializes with default maxTokens', () => {
|
|
15
|
+
const m = new context_manager_1.AIContextManager();
|
|
16
|
+
expect(m.maxTokens).toBe(4096);
|
|
17
|
+
expect(m.messages).toEqual([]);
|
|
18
|
+
expect(m.currentTokens).toBe(0);
|
|
19
|
+
});
|
|
20
|
+
it('initializes with custom maxTokens', () => {
|
|
21
|
+
const m = new context_manager_1.AIContextManager(1000);
|
|
22
|
+
expect(m.maxTokens).toBe(1000);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe('addMessage()', () => {
|
|
26
|
+
it('adds a user message', () => {
|
|
27
|
+
manager.addMessage({ role: 'user', content: 'hello' });
|
|
28
|
+
expect(manager.messages).toHaveLength(1);
|
|
29
|
+
expect(manager.messages[0].content).toBe('hello');
|
|
30
|
+
});
|
|
31
|
+
it('increments currentTokens', () => {
|
|
32
|
+
manager.addMessage({ role: 'user', content: 'hello' });
|
|
33
|
+
expect(manager.currentTokens).toBeGreaterThan(0);
|
|
34
|
+
});
|
|
35
|
+
it('adds message with name field', () => {
|
|
36
|
+
manager.addMessage({ role: 'user', content: 'hi', name: 'Alice' });
|
|
37
|
+
expect(manager.messages[0].name).toBe('Alice');
|
|
38
|
+
});
|
|
39
|
+
it('adds message with functionCall field', () => {
|
|
40
|
+
manager.addMessage({
|
|
41
|
+
role: 'assistant',
|
|
42
|
+
content: '',
|
|
43
|
+
functionCall: { name: 'getWeather', arguments: '{"city":"NYC"}' },
|
|
44
|
+
});
|
|
45
|
+
expect(manager.messages).toHaveLength(1);
|
|
46
|
+
});
|
|
47
|
+
it('auto-trims when content exceeds token limit', () => {
|
|
48
|
+
const tightManager = new context_manager_1.AIContextManager(10);
|
|
49
|
+
tightManager.addMessage({ role: 'user', content: 'a'.repeat(200) });
|
|
50
|
+
expect(tightManager.currentTokens).toBeLessThanOrEqual(10);
|
|
51
|
+
});
|
|
52
|
+
it('adds multiple messages', () => {
|
|
53
|
+
manager.addMessage({ role: 'user', content: 'first' });
|
|
54
|
+
manager.addMessage({ role: 'assistant', content: 'second' });
|
|
55
|
+
expect(manager.messages).toHaveLength(2);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe('getMessages()', () => {
|
|
59
|
+
it('returns a shallow copy of messages', () => {
|
|
60
|
+
manager.addMessage({ role: 'user', content: 'test' });
|
|
61
|
+
const msgs = manager.getMessages();
|
|
62
|
+
msgs.push({ role: 'assistant', content: 'extra' });
|
|
63
|
+
expect(manager.messages).toHaveLength(1);
|
|
64
|
+
});
|
|
65
|
+
it('returns empty array when no messages', () => {
|
|
66
|
+
expect(manager.getMessages()).toEqual([]);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('clear()', () => {
|
|
70
|
+
it('clears all messages and resets token count', () => {
|
|
71
|
+
manager.addMessage({ role: 'user', content: 'test' });
|
|
72
|
+
manager.clear();
|
|
73
|
+
expect(manager.messages).toHaveLength(0);
|
|
74
|
+
expect(manager.currentTokens).toBe(0);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe('trimToLimit()', () => {
|
|
78
|
+
it('preserves system messages', () => {
|
|
79
|
+
const tightManager = new context_manager_1.AIContextManager(50);
|
|
80
|
+
tightManager.addMessage({ role: 'system', content: 'You are a helpful assistant' });
|
|
81
|
+
tightManager.addMessage({ role: 'user', content: 'message 1 to be trimmed maybe' });
|
|
82
|
+
tightManager.addMessage({ role: 'user', content: 'message 2 to be trimmed maybe' });
|
|
83
|
+
tightManager.trimToLimit();
|
|
84
|
+
const systemMsgs = tightManager.getSystemMessages();
|
|
85
|
+
expect(systemMsgs).toHaveLength(1);
|
|
86
|
+
});
|
|
87
|
+
it('keeps most recent conversation messages', () => {
|
|
88
|
+
const tightManager = new context_manager_1.AIContextManager(30);
|
|
89
|
+
tightManager.addMessage({ role: 'user', content: 'old' });
|
|
90
|
+
tightManager.addMessage({ role: 'user', content: 'new' });
|
|
91
|
+
tightManager.trimToLimit();
|
|
92
|
+
const msgs = tightManager.getConversationMessages();
|
|
93
|
+
if (msgs.length > 0) {
|
|
94
|
+
expect(msgs[msgs.length - 1].content).toBe('new');
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
it('trims all conversation messages when system tokens fill the limit', () => {
|
|
98
|
+
const tightManager = new context_manager_1.AIContextManager(5);
|
|
99
|
+
tightManager.addMessage({ role: 'system', content: 'sys' });
|
|
100
|
+
tightManager.addMessage({ role: 'user', content: 'user msg' });
|
|
101
|
+
tightManager.trimToLimit();
|
|
102
|
+
expect(tightManager.getSystemMessages()).toHaveLength(1);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe('getStats()', () => {
|
|
106
|
+
it('returns zero stats when empty', () => {
|
|
107
|
+
const stats = manager.getStats();
|
|
108
|
+
expect(stats.messageCount).toBe(0);
|
|
109
|
+
expect(stats.currentTokens).toBe(0);
|
|
110
|
+
expect(stats.maxTokens).toBe(200);
|
|
111
|
+
expect(stats.utilizationPercent).toBe(0);
|
|
112
|
+
});
|
|
113
|
+
it('returns correct utilization after adding messages', () => {
|
|
114
|
+
manager.addMessage({ role: 'user', content: 'hello world' });
|
|
115
|
+
const stats = manager.getStats();
|
|
116
|
+
expect(stats.messageCount).toBe(1);
|
|
117
|
+
expect(stats.utilizationPercent).toBeGreaterThan(0);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe('setMaxTokens()', () => {
|
|
121
|
+
it('updates maxTokens', () => {
|
|
122
|
+
manager.setMaxTokens(500);
|
|
123
|
+
expect(manager.maxTokens).toBe(500);
|
|
124
|
+
});
|
|
125
|
+
it('trims when new limit is lower than current usage', () => {
|
|
126
|
+
manager.addMessage({ role: 'user', content: 'a'.repeat(200) });
|
|
127
|
+
manager.setMaxTokens(10);
|
|
128
|
+
expect(manager.currentTokens).toBeLessThanOrEqual(10);
|
|
129
|
+
});
|
|
130
|
+
it('does not trim when new limit is higher', () => {
|
|
131
|
+
manager.addMessage({ role: 'user', content: 'hello' });
|
|
132
|
+
const tokensBefore = manager.currentTokens;
|
|
133
|
+
manager.setMaxTokens(1000);
|
|
134
|
+
expect(manager.currentTokens).toBe(tokensBefore);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
describe('getSystemMessages()', () => {
|
|
138
|
+
it('returns only system messages', () => {
|
|
139
|
+
manager.addMessage({ role: 'system', content: 'system prompt' });
|
|
140
|
+
manager.addMessage({ role: 'user', content: 'user msg' });
|
|
141
|
+
expect(manager.getSystemMessages()).toHaveLength(1);
|
|
142
|
+
expect(manager.getSystemMessages()[0].role).toBe('system');
|
|
143
|
+
});
|
|
144
|
+
it('returns empty array when no system messages', () => {
|
|
145
|
+
manager.addMessage({ role: 'user', content: 'user' });
|
|
146
|
+
expect(manager.getSystemMessages()).toHaveLength(0);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
describe('getConversationMessages()', () => {
|
|
150
|
+
it('returns user and assistant messages only', () => {
|
|
151
|
+
manager.addMessage({ role: 'system', content: 'system' });
|
|
152
|
+
manager.addMessage({ role: 'user', content: 'user' });
|
|
153
|
+
manager.addMessage({ role: 'assistant', content: 'assistant' });
|
|
154
|
+
const conv = manager.getConversationMessages();
|
|
155
|
+
expect(conv).toHaveLength(2);
|
|
156
|
+
expect(conv.every((m) => m.role === 'user' || m.role === 'assistant')).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
describe('addSystemMessage()', () => {
|
|
160
|
+
it('adds a system message', () => {
|
|
161
|
+
manager.addSystemMessage('You are helpful');
|
|
162
|
+
expect(manager.messages[0].role).toBe('system');
|
|
163
|
+
expect(manager.messages[0].content).toBe('You are helpful');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
describe('addUserMessage()', () => {
|
|
167
|
+
it('adds a user message', () => {
|
|
168
|
+
manager.addUserMessage('What is the weather?');
|
|
169
|
+
expect(manager.messages[0].role).toBe('user');
|
|
170
|
+
expect(manager.messages[0].content).toBe('What is the weather?');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
describe('addAssistantMessage()', () => {
|
|
174
|
+
it('adds an assistant message', () => {
|
|
175
|
+
manager.addAssistantMessage('It is sunny');
|
|
176
|
+
expect(manager.messages[0].role).toBe('assistant');
|
|
177
|
+
expect(manager.messages[0].content).toBe('It is sunny');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { AIModule } from './ai.module';
|
|
5
5
|
export { AIService } from './ai.service';
|
|
6
|
+
export { AIEnhancedService } from './ai-enhanced.service';
|
|
6
7
|
export type { AITaskConfig, AITaskContext, AITaskResult } from './ai.types';
|
|
7
8
|
export { AITask } from './ai.decorator';
|
|
8
9
|
export { OpenAIProvider } from './providers/openai.provider';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAGxC,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EACL,UAAU,EACV,QAAQ,EACR,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,EACvB,+BAA+B,GAChC,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EACL,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,UAAU,IAAI,cAAc,EACjC,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC5E,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAGxC,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EACL,UAAU,EACV,QAAQ,EACR,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,EACvB,+BAA+B,GAChC,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EACL,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,UAAU,IAAI,cAAc,EACjC,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
* @hazeljs/ai - AI integration module for HazelJS
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.VectorService = exports.getAIPropertyValidationMetadata = exports.hasAIValidationMetadata = exports.getAIValidationMetadata = exports.AIValidateProperty = exports.AIValidate = exports.getAIPromptMetadata = exports.hasAIFunctionMetadata = exports.getAIFunctionMetadata = exports.AIPrompt = exports.AIFunction = exports.OllamaProvider = exports.CohereProvider = exports.GeminiProvider = exports.AnthropicProvider = exports.OpenAIProvider = exports.AITask = exports.AIService = exports.AIModule = void 0;
|
|
6
|
+
exports.VectorService = exports.getAIPropertyValidationMetadata = exports.hasAIValidationMetadata = exports.getAIValidationMetadata = exports.AIValidateProperty = exports.AIValidate = exports.getAIPromptMetadata = exports.hasAIFunctionMetadata = exports.getAIFunctionMetadata = exports.AIPrompt = exports.AIFunction = exports.OllamaProvider = exports.CohereProvider = exports.GeminiProvider = exports.AnthropicProvider = exports.OpenAIProvider = exports.AITask = exports.AIEnhancedService = exports.AIService = exports.AIModule = void 0;
|
|
7
7
|
// AI Module
|
|
8
8
|
var ai_module_1 = require("./ai.module");
|
|
9
9
|
Object.defineProperty(exports, "AIModule", { enumerable: true, get: function () { return ai_module_1.AIModule; } });
|
|
10
10
|
var ai_service_1 = require("./ai.service");
|
|
11
11
|
Object.defineProperty(exports, "AIService", { enumerable: true, get: function () { return ai_service_1.AIService; } });
|
|
12
|
+
var ai_enhanced_service_1 = require("./ai-enhanced.service");
|
|
13
|
+
Object.defineProperty(exports, "AIEnhancedService", { enumerable: true, get: function () { return ai_enhanced_service_1.AIEnhancedService; } });
|
|
12
14
|
var ai_decorator_1 = require("./ai.decorator");
|
|
13
15
|
Object.defineProperty(exports, "AITask", { enumerable: true, get: function () { return ai_decorator_1.AITask; } });
|
|
14
16
|
// Enhanced AI
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PromptTemplate } from '@hazeljs/prompts';
|
|
2
|
+
export declare const AI_TASK_FORMAT_KEY = "ai:task:format";
|
|
3
|
+
export interface AITaskFormatVariables {
|
|
4
|
+
taskName: string;
|
|
5
|
+
description: string;
|
|
6
|
+
input: string;
|
|
7
|
+
inputExample: string;
|
|
8
|
+
outputExample: string;
|
|
9
|
+
}
|
|
10
|
+
declare const template: PromptTemplate<AITaskFormatVariables>;
|
|
11
|
+
export { template as aiTaskFormatPrompt };
|
|
12
|
+
//# sourceMappingURL=task.prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task.prompt.d.ts","sourceRoot":"","sources":["../../src/prompts/task.prompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAkB,MAAM,kBAAkB,CAAC;AAElE,eAAO,MAAM,kBAAkB,mBAAmB,CAAC;AAEnD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,QAAA,MAAM,QAAQ,uCAKZ,CAAC;AAIH,OAAO,EAAE,QAAQ,IAAI,kBAAkB,EAAE,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.aiTaskFormatPrompt = exports.AI_TASK_FORMAT_KEY = void 0;
|
|
4
|
+
const prompts_1 = require("@hazeljs/prompts");
|
|
5
|
+
exports.AI_TASK_FORMAT_KEY = 'ai:task:format';
|
|
6
|
+
const template = new prompts_1.PromptTemplate(`{description}`, {
|
|
7
|
+
name: 'AI Task Format',
|
|
8
|
+
description: 'Formats an AI task prompt by substituting context variables into the template string',
|
|
9
|
+
version: '1.0.0',
|
|
10
|
+
});
|
|
11
|
+
exports.aiTaskFormatPrompt = template;
|
|
12
|
+
prompts_1.PromptRegistry.register(exports.AI_TASK_FORMAT_KEY, template);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic.provider.test.d.ts","sourceRoot":"","sources":["../../src/providers/anthropic.provider.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
jest.mock('@hazeljs/core', () => ({
|
|
4
|
+
__esModule: true,
|
|
5
|
+
default: { info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() },
|
|
6
|
+
}));
|
|
7
|
+
const mockMessagesCreate = jest.fn();
|
|
8
|
+
const mockMessagesStream = jest.fn();
|
|
9
|
+
jest.mock('@anthropic-ai/sdk', () => ({
|
|
10
|
+
__esModule: true,
|
|
11
|
+
default: jest.fn().mockImplementation(() => ({
|
|
12
|
+
messages: {
|
|
13
|
+
create: mockMessagesCreate,
|
|
14
|
+
stream: mockMessagesStream,
|
|
15
|
+
},
|
|
16
|
+
})),
|
|
17
|
+
}));
|
|
18
|
+
const anthropic_provider_1 = require("./anthropic.provider");
|
|
19
|
+
const BASE_REQUEST = {
|
|
20
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
21
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
22
|
+
};
|
|
23
|
+
const MOCK_RESPONSE = {
|
|
24
|
+
id: 'msg_001',
|
|
25
|
+
content: [{ type: 'text', text: 'Hello there!' }],
|
|
26
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
27
|
+
stop_reason: 'end_turn',
|
|
28
|
+
usage: { input_tokens: 10, output_tokens: 15 },
|
|
29
|
+
};
|
|
30
|
+
describe('AnthropicProvider', () => {
|
|
31
|
+
let provider;
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
jest.clearAllMocks();
|
|
34
|
+
provider = new anthropic_provider_1.AnthropicProvider('test-api-key');
|
|
35
|
+
});
|
|
36
|
+
describe('constructor', () => {
|
|
37
|
+
it('sets name to anthropic', () => {
|
|
38
|
+
expect(provider.name).toBe('anthropic');
|
|
39
|
+
});
|
|
40
|
+
it('warns when no API key provided', () => {
|
|
41
|
+
new anthropic_provider_1.AnthropicProvider();
|
|
42
|
+
// Constructor runs without throwing
|
|
43
|
+
});
|
|
44
|
+
it('uses ANTHROPIC_API_KEY env var', () => {
|
|
45
|
+
process.env.ANTHROPIC_API_KEY = 'env-key';
|
|
46
|
+
const p = new anthropic_provider_1.AnthropicProvider();
|
|
47
|
+
expect(p).toBeDefined();
|
|
48
|
+
delete process.env.ANTHROPIC_API_KEY;
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('getSupportedModels()', () => {
|
|
52
|
+
it('returns a list of claude models', () => {
|
|
53
|
+
const models = provider.getSupportedModels();
|
|
54
|
+
expect(models).toContain('claude-3-5-sonnet-20241022');
|
|
55
|
+
expect(models.length).toBeGreaterThan(0);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe('complete()', () => {
|
|
59
|
+
it('returns a completion response', async () => {
|
|
60
|
+
mockMessagesCreate.mockResolvedValue(MOCK_RESPONSE);
|
|
61
|
+
const result = await provider.complete(BASE_REQUEST);
|
|
62
|
+
expect(result.content).toBe('Hello there!');
|
|
63
|
+
expect(result.role).toBe('assistant');
|
|
64
|
+
expect(result.usage?.promptTokens).toBe(10);
|
|
65
|
+
expect(result.usage?.completionTokens).toBe(15);
|
|
66
|
+
expect(result.usage?.totalTokens).toBe(25);
|
|
67
|
+
expect(result.finishReason).toBe('end_turn');
|
|
68
|
+
});
|
|
69
|
+
it('uses default model when not specified', async () => {
|
|
70
|
+
mockMessagesCreate.mockResolvedValue(MOCK_RESPONSE);
|
|
71
|
+
await provider.complete({ messages: [{ role: 'user', content: 'hi' }] });
|
|
72
|
+
expect(mockMessagesCreate).toHaveBeenCalledWith(expect.objectContaining({ model: 'claude-3-5-sonnet-20241022' }));
|
|
73
|
+
});
|
|
74
|
+
it('passes maxTokens when specified', async () => {
|
|
75
|
+
mockMessagesCreate.mockResolvedValue(MOCK_RESPONSE);
|
|
76
|
+
await provider.complete({ ...BASE_REQUEST, maxTokens: 500 });
|
|
77
|
+
expect(mockMessagesCreate).toHaveBeenCalledWith(expect.objectContaining({ max_tokens: 500 }));
|
|
78
|
+
});
|
|
79
|
+
it('separates system messages from conversation', async () => {
|
|
80
|
+
mockMessagesCreate.mockResolvedValue(MOCK_RESPONSE);
|
|
81
|
+
await provider.complete({
|
|
82
|
+
messages: [
|
|
83
|
+
{ role: 'system', content: 'You are helpful.' },
|
|
84
|
+
{ role: 'user', content: 'Hello' },
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
expect(mockMessagesCreate).toHaveBeenCalledWith(expect.objectContaining({
|
|
88
|
+
system: 'You are helpful.',
|
|
89
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
90
|
+
}));
|
|
91
|
+
});
|
|
92
|
+
it('handles response with no stop_reason', async () => {
|
|
93
|
+
mockMessagesCreate.mockResolvedValue({ ...MOCK_RESPONSE, stop_reason: null });
|
|
94
|
+
const result = await provider.complete(BASE_REQUEST);
|
|
95
|
+
expect(result.finishReason).toBe('end_turn');
|
|
96
|
+
});
|
|
97
|
+
it('concatenates multiple text content blocks', async () => {
|
|
98
|
+
mockMessagesCreate.mockResolvedValue({
|
|
99
|
+
...MOCK_RESPONSE,
|
|
100
|
+
content: [
|
|
101
|
+
{ type: 'text', text: 'Part 1. ' },
|
|
102
|
+
{ type: 'text', text: 'Part 2.' },
|
|
103
|
+
],
|
|
104
|
+
});
|
|
105
|
+
const result = await provider.complete(BASE_REQUEST);
|
|
106
|
+
expect(result.content).toBe('Part 1. Part 2.');
|
|
107
|
+
});
|
|
108
|
+
it('ignores non-text content blocks', async () => {
|
|
109
|
+
mockMessagesCreate.mockResolvedValue({
|
|
110
|
+
...MOCK_RESPONSE,
|
|
111
|
+
content: [
|
|
112
|
+
{ type: 'tool_use', id: 'tool_1' },
|
|
113
|
+
{ type: 'text', text: 'Some text' },
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
const result = await provider.complete(BASE_REQUEST);
|
|
117
|
+
expect(result.content).toBe('Some text');
|
|
118
|
+
});
|
|
119
|
+
it('throws wrapped error on API failure', async () => {
|
|
120
|
+
mockMessagesCreate.mockRejectedValue(new Error('Rate limit exceeded'));
|
|
121
|
+
await expect(provider.complete(BASE_REQUEST)).rejects.toThrow('Anthropic API error: Rate limit exceeded');
|
|
122
|
+
});
|
|
123
|
+
it('handles non-Error thrown objects', async () => {
|
|
124
|
+
mockMessagesCreate.mockRejectedValue('string error');
|
|
125
|
+
await expect(provider.complete(BASE_REQUEST)).rejects.toThrow('Anthropic API error: Unknown error');
|
|
126
|
+
});
|
|
127
|
+
it('sets undefined system when no system messages', async () => {
|
|
128
|
+
mockMessagesCreate.mockResolvedValue(MOCK_RESPONSE);
|
|
129
|
+
await provider.complete({ messages: [{ role: 'user', content: 'hi' }] });
|
|
130
|
+
expect(mockMessagesCreate).toHaveBeenCalledWith(expect.objectContaining({ system: undefined }));
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
describe('streamComplete()', () => {
|
|
134
|
+
function makeStreamEvents(events) {
|
|
135
|
+
return (async function* () {
|
|
136
|
+
for (const ev of events) {
|
|
137
|
+
yield ev;
|
|
138
|
+
}
|
|
139
|
+
})();
|
|
140
|
+
}
|
|
141
|
+
it('yields chunks for message_start and content_block_delta events', async () => {
|
|
142
|
+
mockMessagesStream.mockReturnValue(makeStreamEvents([
|
|
143
|
+
{ type: 'message_start', message: { id: 'msg_1', usage: { input_tokens: 5 } } },
|
|
144
|
+
{ type: 'content_block_delta', delta: { type: 'text_delta', text: 'Hello' } },
|
|
145
|
+
{ type: 'content_block_delta', delta: { type: 'text_delta', text: ' world' } },
|
|
146
|
+
{ type: 'message_delta', usage: { output_tokens: 10 } },
|
|
147
|
+
{ type: 'message_stop' },
|
|
148
|
+
]));
|
|
149
|
+
const results = [];
|
|
150
|
+
for await (const chunk of provider.streamComplete(BASE_REQUEST)) {
|
|
151
|
+
results.push(chunk);
|
|
152
|
+
}
|
|
153
|
+
// 2 content deltas + 1 message_stop
|
|
154
|
+
expect(results.length).toBeGreaterThan(0);
|
|
155
|
+
});
|
|
156
|
+
it('includes usage in final chunk', async () => {
|
|
157
|
+
mockMessagesStream.mockReturnValue(makeStreamEvents([
|
|
158
|
+
{ type: 'message_start', message: { id: 'msg_2', usage: { input_tokens: 8 } } },
|
|
159
|
+
{ type: 'message_delta', usage: { output_tokens: 12 } },
|
|
160
|
+
{ type: 'message_stop' },
|
|
161
|
+
]));
|
|
162
|
+
const results = [];
|
|
163
|
+
for await (const chunk of provider.streamComplete(BASE_REQUEST)) {
|
|
164
|
+
results.push(chunk);
|
|
165
|
+
}
|
|
166
|
+
const finalChunk = results[results.length - 1];
|
|
167
|
+
expect(finalChunk.done).toBe(true);
|
|
168
|
+
expect(finalChunk.usage).toBeDefined();
|
|
169
|
+
});
|
|
170
|
+
it('ignores non-text_delta content blocks', async () => {
|
|
171
|
+
mockMessagesStream.mockReturnValue(makeStreamEvents([
|
|
172
|
+
{ type: 'content_block_delta', delta: { type: 'input_json_delta', partial_json: '{}' } },
|
|
173
|
+
{ type: 'message_stop' },
|
|
174
|
+
]));
|
|
175
|
+
const results = [];
|
|
176
|
+
for await (const chunk of provider.streamComplete(BASE_REQUEST)) {
|
|
177
|
+
results.push(chunk);
|
|
178
|
+
}
|
|
179
|
+
// Only message_stop chunk
|
|
180
|
+
expect(results).toHaveLength(1);
|
|
181
|
+
});
|
|
182
|
+
it('uses default model for streaming', async () => {
|
|
183
|
+
mockMessagesStream.mockReturnValue(makeStreamEvents([{ type: 'message_stop' }]));
|
|
184
|
+
for await (const _chunk of provider.streamComplete({
|
|
185
|
+
messages: [{ role: 'user', content: 'hi' }],
|
|
186
|
+
})) {
|
|
187
|
+
// consume
|
|
188
|
+
}
|
|
189
|
+
expect(mockMessagesStream).toHaveBeenCalledWith(expect.objectContaining({ model: 'claude-3-5-sonnet-20241022' }));
|
|
190
|
+
});
|
|
191
|
+
it('throws wrapped error on streaming failure', async () => {
|
|
192
|
+
mockMessagesStream.mockImplementation(async function* () {
|
|
193
|
+
throw new Error('Stream error');
|
|
194
|
+
yield { type: 'message_stop' };
|
|
195
|
+
});
|
|
196
|
+
await expect(async () => {
|
|
197
|
+
for await (const _chunk of provider.streamComplete(BASE_REQUEST)) {
|
|
198
|
+
// consume
|
|
199
|
+
}
|
|
200
|
+
}).rejects.toThrow('Anthropic streaming error: Stream error');
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
describe('embed()', () => {
|
|
204
|
+
it('throws not supported error', async () => {
|
|
205
|
+
await expect(provider.embed({ input: 'test' })).rejects.toThrow('Anthropic does not support embeddings');
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
describe('isAvailable()', () => {
|
|
209
|
+
it('returns false when no API key', async () => {
|
|
210
|
+
const p = new anthropic_provider_1.AnthropicProvider('');
|
|
211
|
+
expect(await p.isAvailable()).toBe(false);
|
|
212
|
+
});
|
|
213
|
+
it('returns true when API responds successfully', async () => {
|
|
214
|
+
mockMessagesCreate.mockResolvedValue(MOCK_RESPONSE);
|
|
215
|
+
expect(await provider.isAvailable()).toBe(true);
|
|
216
|
+
});
|
|
217
|
+
it('returns false on API error', async () => {
|
|
218
|
+
mockMessagesCreate.mockRejectedValue(new Error('Unauthorized'));
|
|
219
|
+
expect(await provider.isAvailable()).toBe(false);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cohere.provider.test.d.ts","sourceRoot":"","sources":["../../src/providers/cohere.provider.test.ts"],"names":[],"mappings":""}
|