@game_ryo/lsji 0.1.0 → 0.3.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.
Files changed (75) hide show
  1. package/package.json +15 -7
  2. package/src/cli.js +395 -62
  3. package/src/execution/budget/circuit-breaker.js +245 -0
  4. package/src/execution/budget/cost-tracker.js +387 -0
  5. package/src/execution/budget/index.js +63 -0
  6. package/src/execution/budget/token-counter.js +159 -0
  7. package/src/execution/engine.js +428 -0
  8. package/src/execution/hitl/approval-gate.js +210 -0
  9. package/src/execution/hitl/index.js +12 -0
  10. package/src/execution/hitl/notifier.js +151 -0
  11. package/src/execution/hitl/store.js +311 -0
  12. package/src/execution/idempotency.js +312 -0
  13. package/src/execution/index.js +14 -0
  14. package/src/index.js +80 -4
  15. package/src/llm/index.js +21 -0
  16. package/src/llm/llm-agent.js +357 -0
  17. package/src/llm/memory/conversation.js +271 -0
  18. package/src/llm/memory/episodic.js +312 -0
  19. package/src/llm/memory/index.js +12 -0
  20. package/src/llm/memory/semantic.js +324 -0
  21. package/src/llm/plugins/index.js +202 -0
  22. package/src/llm/prompt-manager.js +332 -0
  23. package/src/llm/providers/anthropic.js +250 -0
  24. package/src/llm/providers/base.js +116 -0
  25. package/src/llm/providers/local.js +163 -0
  26. package/src/llm/providers/openai.js +212 -0
  27. package/src/llm/tools/registry.js +342 -0
  28. package/src/server/index.js +416 -0
  29. package/src/server/ui/index.html +16 -0
  30. package/src/server/ui/package.json +19 -0
  31. package/src/server/ui/src/main.jsx +10 -0
  32. package/src/server/ui/src/styles.css +260 -0
  33. package/src/server/ui/vite.config.js +27 -0
  34. package/docs/README.md +0 -43
  35. package/docs/blog/2019-05-28-first-blog-post.mdx +0 -12
  36. package/docs/blog/2019-05-29-long-blog-post.mdx +0 -44
  37. package/docs/blog/2021-08-01-mdx-blog-post.mdx +0 -24
  38. package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  39. package/docs/blog/2021-08-26-welcome/index.mdx +0 -29
  40. package/docs/blog/authors.yml +0 -25
  41. package/docs/blog/tags.yml +0 -19
  42. package/docs/docs/api/agent.md +0 -151
  43. package/docs/docs/api/env.md +0 -133
  44. package/docs/docs/api/environments.md +0 -102
  45. package/docs/docs/api/qlearning.md +0 -138
  46. package/docs/docs/api/storage.md +0 -168
  47. package/docs/docs/architecture.md +0 -155
  48. package/docs/docs/cli.md +0 -210
  49. package/docs/docs/contributing.md +0 -162
  50. package/docs/docs/core-concepts.md +0 -152
  51. package/docs/docs/examples/advanced-training.md +0 -244
  52. package/docs/docs/examples/custom-environment.md +0 -198
  53. package/docs/docs/examples/custom-storage.md +0 -251
  54. package/docs/docs/getting-started.md +0 -91
  55. package/docs/docusaurus.config.ts +0 -149
  56. package/docs/package-lock.json +0 -19522
  57. package/docs/package.json +0 -49
  58. package/docs/sidebars.ts +0 -33
  59. package/docs/src/components/HomepageFeatures/index.tsx +0 -71
  60. package/docs/src/components/HomepageFeatures/styles.module.css +0 -11
  61. package/docs/src/css/custom.css +0 -79
  62. package/docs/src/pages/index.module.css +0 -23
  63. package/docs/src/pages/index.tsx +0 -44
  64. package/docs/src/pages/markdown-page.mdx +0 -7
  65. package/docs/static/.nojekyll +0 -0
  66. package/docs/static/img/docusaurus-social-card.jpg +0 -0
  67. package/docs/static/img/docusaurus.png +0 -0
  68. package/docs/static/img/favicon.ico +0 -0
  69. package/docs/static/img/logo.png +0 -0
  70. package/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
  71. package/docs/static/img/undraw_docusaurus_react.svg +0 -170
  72. package/docs/static/img/undraw_docusaurus_tree.svg +0 -40
  73. package/docs/tsconfig.json +0 -12
  74. package/legacy/worker.js +0 -166
  75. package/legacy/wrangler.toml +0 -11
@@ -0,0 +1,212 @@
1
+ /**
2
+ * OpenAI Provider Implementation
3
+ *
4
+ * Supports OpenAI API (GPT-4, GPT-3.5, etc.)
5
+ * Compatible with OpenAI-compatible endpoints (e.g., Azure, local proxies)
6
+ */
7
+
8
+ import { LLMProvider } from './base.js';
9
+
10
+ // OpenAI pricing (USD per 1M tokens) - as of 2026
11
+ const PRICING = {
12
+ 'gpt-4o': { input: 5.00, output: 15.00 },
13
+ 'gpt-4o-mini': { input: 0.15, output: 0.60 },
14
+ 'gpt-4-turbo': { input: 10.00, output: 30.00 },
15
+ 'gpt-4': { input: 30.00, output: 60.00 },
16
+ 'gpt-3.5-turbo': { input: 0.50, output: 1.50 },
17
+ 'o1-preview': { input: 15.00, output: 60.00 },
18
+ 'o1-mini': { input: 3.00, output: 12.00 },
19
+ };
20
+
21
+ export class OpenAIProvider extends LLMProvider {
22
+ constructor(config = {}) {
23
+ super({
24
+ model: config.model || 'gpt-4o-mini',
25
+ apiKey: config.apiKey || process.env.OPENAI_API_KEY,
26
+ baseUrl: config.baseUrl || 'https://api.openai.com/v1',
27
+ defaultParams: config.defaultParams || {},
28
+ });
29
+
30
+ this.pricing = PRICING[this.model] || PRICING['gpt-4o-mini'];
31
+ this.client = null;
32
+ }
33
+
34
+ /**
35
+ * Lazy-load OpenAI client
36
+ */
37
+ async getClient() {
38
+ if (!this.client) {
39
+ const { default: OpenAI } = await import('openai');
40
+ this.client = new OpenAI({
41
+ apiKey: this.apiKey,
42
+ baseURL: this.baseUrl,
43
+ });
44
+ }
45
+ return this.client;
46
+ }
47
+
48
+ /**
49
+ * Generate completion from OpenAI
50
+ */
51
+ async generate(messages, options = {}) {
52
+ const client = await this.getClient();
53
+
54
+ const params = {
55
+ model: this.model,
56
+ messages: this.formatMessages(messages),
57
+ temperature: options.temperature ?? this.defaultParams.temperature ?? 0.7,
58
+ max_tokens: options.maxTokens ?? this.defaultParams.maxTokens ?? 4096,
59
+ ...this.defaultParams,
60
+ };
61
+
62
+ if (options.tools && options.tools.length > 0) {
63
+ params.tools = this.formatTools(options.tools);
64
+ params.tool_choice = options.toolChoice || 'auto';
65
+ }
66
+
67
+ const response = await client.chat.completions.create(params);
68
+
69
+ return this.parseResponse(response);
70
+ }
71
+
72
+ /**
73
+ * Generate streaming completion
74
+ */
75
+ async *generateStream(messages, options = {}) {
76
+ const client = await this.getClient();
77
+
78
+ const params = {
79
+ model: this.model,
80
+ messages: this.formatMessages(messages),
81
+ temperature: options.temperature ?? this.defaultParams.temperature ?? 0.7,
82
+ max_tokens: options.maxTokens ?? this.defaultParams.maxTokens ?? 4096,
83
+ stream: true,
84
+ ...this.defaultParams,
85
+ };
86
+
87
+ if (options.tools && options.tools.length > 0) {
88
+ params.tools = this.formatTools(options.tools);
89
+ params.tool_choice = options.toolChoice || 'auto';
90
+ }
91
+
92
+ const stream = await client.chat.completions.create(params);
93
+
94
+ for await (const chunk of stream) {
95
+ yield this.parseStreamChunk(chunk);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Format messages for OpenAI API
101
+ */
102
+ formatMessages(messages) {
103
+ return messages.map(msg => ({
104
+ role: msg.role,
105
+ content: msg.content,
106
+ name: msg.name,
107
+ tool_call_id: msg.tool_call_id,
108
+ tool_calls: msg.tool_calls,
109
+ }));
110
+ }
111
+
112
+ /**
113
+ * Format tools for OpenAI API
114
+ */
115
+ formatTools(tools) {
116
+ return tools.map(tool => ({
117
+ type: 'function',
118
+ function: {
119
+ name: tool.name,
120
+ description: tool.description,
121
+ parameters: tool.parameters,
122
+ },
123
+ }));
124
+ }
125
+
126
+ /**
127
+ * Parse OpenAI response
128
+ */
129
+ parseResponse(response) {
130
+ const choice = response.choices[0];
131
+ const message = choice.message;
132
+
133
+ return {
134
+ content: message.content || '',
135
+ toolCalls: message.tool_calls || [],
136
+ usage: {
137
+ inputTokens: response.usage?.prompt_tokens || 0,
138
+ outputTokens: response.usage?.completion_tokens || 0,
139
+ totalTokens: response.usage?.total_tokens || 0,
140
+ },
141
+ finishReason: choice.finish_reason,
142
+ model: response.model,
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Parse streaming chunk
148
+ */
149
+ parseStreamChunk(chunk) {
150
+ const choice = chunk.choices[0];
151
+ const delta = choice.delta;
152
+
153
+ return {
154
+ content: delta.content || '',
155
+ toolCalls: delta.tool_calls || [],
156
+ usage: chunk.usage ? {
157
+ inputTokens: chunk.usage.prompt_tokens || 0,
158
+ outputTokens: chunk.usage.completion_tokens || 0,
159
+ totalTokens: chunk.usage.total_tokens || 0,
160
+ } : null,
161
+ finishReason: choice.finish_reason,
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Calculate cost for usage
167
+ */
168
+ calculateCost(usage) {
169
+ const inputCost = (usage.inputTokens / 1_000_000) * this.pricing.input;
170
+ const outputCost = (usage.outputTokens / 1_000_000) * this.pricing.output;
171
+ return inputCost + outputCost;
172
+ }
173
+
174
+ /**
175
+ * Get pricing info for current model
176
+ */
177
+ getPricing() {
178
+ return { ...this.pricing };
179
+ }
180
+
181
+ /**
182
+ * Check if provider supports function calling
183
+ */
184
+ supportsTools() {
185
+ // Most OpenAI models support function calling
186
+ return !this.model.includes('o1-mini') && !this.model.includes('o1-preview');
187
+ }
188
+
189
+ /**
190
+ * Check if provider supports streaming
191
+ */
192
+ supportsStreaming() {
193
+ return true;
194
+ }
195
+
196
+ /**
197
+ * Validate configuration
198
+ */
199
+ async validate() {
200
+ if (!this.apiKey) {
201
+ return { valid: false, error: 'OpenAI API key is required' };
202
+ }
203
+
204
+ try {
205
+ const client = await this.getClient();
206
+ await client.models.list();
207
+ return { valid: true };
208
+ } catch (e) {
209
+ return { valid: false, error: e.message };
210
+ }
211
+ }
212
+ }
@@ -0,0 +1,342 @@
1
+ /**
2
+ * Tool Registry
3
+ *
4
+ * Manages tool/function definitions for LLM agents.
5
+ * Provides registration, lookup, and execution with idempotency.
6
+ */
7
+
8
+ import { IdempotencyStore } from '../../execution/idempotency.js';
9
+ import { v4 as uuidv4 } from 'uuid';
10
+
11
+ /**
12
+ * Tool definition
13
+ * @typedef {Object} ToolDefinition
14
+ * @property {string} name - Unique tool name
15
+ * @property {string} description - Human-readable description
16
+ * @property {Object} parameters - JSON Schema for parameters
17
+ * @property {Function} execute - Async function(params, context) -> result
18
+ * @property {boolean} [requiresApproval] - Whether HITL approval needed
19
+ * @property {boolean} [idempotent] - Whether to use idempotency
20
+ */
21
+
22
+ /**
23
+ * Tool Registry - Manages available tools
24
+ */
25
+ export class ToolRegistry {
26
+ constructor({ idempotencyStore, approvalGate } = {}) {
27
+ this.tools = new Map();
28
+ this.idempotencyStore = idempotencyStore;
29
+ this.approvalGate = approvalGate;
30
+ this.categories = new Map();
31
+ }
32
+
33
+ /**
34
+ * Register a tool
35
+ */
36
+ register(definition) {
37
+ if (!definition.name) {
38
+ throw new Error('Tool must have a name');
39
+ }
40
+ if (!definition.execute || typeof definition.execute !== 'function') {
41
+ throw new Error('Tool must have an execute function');
42
+ }
43
+ if (!definition.parameters) {
44
+ throw new Error('Tool must have parameters schema');
45
+ }
46
+
47
+ this.tools.set(definition.name, definition);
48
+
49
+ // Categorize
50
+ const category = definition.category || 'general';
51
+ if (!this.categories.has(category)) {
52
+ this.categories.set(category, []);
53
+ }
54
+ this.categories.get(category).push(definition.name);
55
+
56
+ return this;
57
+ }
58
+
59
+ /**
60
+ * Register multiple tools
61
+ */
62
+ registerMany(definitions) {
63
+ for (const def of definitions) {
64
+ this.register(def);
65
+ }
66
+ return this;
67
+ }
68
+
69
+ /**
70
+ * Get a tool by name
71
+ */
72
+ get(name) {
73
+ return this.tools.get(name);
74
+ }
75
+
76
+ /**
77
+ * Check if tool exists
78
+ */
79
+ has(name) {
80
+ return this.tools.has(name);
81
+ }
82
+
83
+ /**
84
+ * Get all tools
85
+ */
86
+ getAll() {
87
+ return Array.from(this.tools.values());
88
+ }
89
+
90
+ /**
91
+ * Get tools by category
92
+ */
93
+ getByCategory(category) {
94
+ const names = this.categories.get(category) || [];
95
+ return names.map(n => this.tools.get(n)).filter(Boolean);
96
+ }
97
+
98
+ /**
99
+ * Get tool definitions for LLM (OpenAI/Anthropic format)
100
+ */
101
+ getDefinitions(category = null) {
102
+ const tools = category ? this.getByCategory(category) : this.getAll();
103
+ return tools.map(tool => ({
104
+ type: 'function',
105
+ function: {
106
+ name: tool.name,
107
+ description: tool.description,
108
+ parameters: tool.parameters,
109
+ },
110
+ }));
111
+ }
112
+
113
+ /**
114
+ * Execute a tool with idempotency and approval
115
+ */
116
+ async execute(name, params, context = {}) {
117
+ const tool = this.tools.get(name);
118
+ if (!tool) {
119
+ throw new Error(`Tool not found: ${name}`);
120
+ }
121
+
122
+ // Check approval requirement
123
+ if (tool.requiresApproval && this.approvalGate) {
124
+ const approval = await this.approvalGate.requestApproval({
125
+ action: name,
126
+ context: { params, ...context },
127
+ requester: context.requester || 'agent',
128
+ });
129
+
130
+ if (approval.status !== 'approved') {
131
+ throw new Error(`Tool ${name} was ${approval.status}: ${approval.reason}`);
132
+ }
133
+ }
134
+
135
+ // Execute with idempotency if enabled
136
+ if (tool.idempotent && this.idempotencyStore) {
137
+ const key = `${name}_${uuidv4()}`;
138
+ return this.idempotencyStore.execute(key, name, params, () => tool.execute(params, context));
139
+ }
140
+
141
+ return tool.execute(params, context);
142
+ }
143
+
144
+ /**
145
+ * Execute multiple tools in parallel
146
+ */
147
+ async executeAll(calls, context = {}) {
148
+ return Promise.all(calls.map(({ name, params }) => this.execute(name, params, context)));
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Create tool registry with built-in tools
154
+ */
155
+ export function createToolRegistry(config = {}) {
156
+ const registry = new ToolRegistry(config);
157
+
158
+ // Register built-in tools
159
+ registry.registerMany(getBuiltinTools(config));
160
+
161
+ return registry;
162
+ }
163
+
164
+ /**
165
+ * Get built-in tool definitions
166
+ */
167
+ function getBuiltinTools(config = {}) {
168
+ const tools = [];
169
+
170
+ // Web search tool
171
+ tools.push({
172
+ name: 'web_search',
173
+ description: 'Search the web for current information',
174
+ category: 'research',
175
+ parameters: {
176
+ type: 'object',
177
+ properties: {
178
+ query: { type: 'string', description: 'Search query' },
179
+ maxResults: { type: 'number', description: 'Maximum results', default: 5 },
180
+ },
181
+ required: ['query'],
182
+ },
183
+ requiresApproval: false,
184
+ idempotent: true,
185
+ async execute({ query, maxResults = 5 }, context) {
186
+ // This would integrate with actual search API
187
+ return { query, results: [], note: 'Web search not configured' };
188
+ },
189
+ });
190
+
191
+ // File read tool
192
+ tools.push({
193
+ name: 'file_read',
194
+ description: 'Read a file from the filesystem',
195
+ category: 'filesystem',
196
+ parameters: {
197
+ type: 'object',
198
+ properties: {
199
+ path: { type: 'string', description: 'File path' },
200
+ encoding: { type: 'string', description: 'Encoding', default: 'utf-8' },
201
+ },
202
+ required: ['path'],
203
+ },
204
+ requiresApproval: false,
205
+ idempotent: true,
206
+ async execute({ path, encoding = 'utf-8' }, context) {
207
+ const fs = await import('fs/promises');
208
+ return fs.readFile(path, encoding);
209
+ },
210
+ });
211
+
212
+ // File write tool
213
+ tools.push({
214
+ name: 'file_write',
215
+ description: 'Write a file to the filesystem',
216
+ category: 'filesystem',
217
+ parameters: {
218
+ type: 'object',
219
+ properties: {
220
+ path: { type: 'string', description: 'File path' },
221
+ content: { type: 'string', description: 'File content' },
222
+ encoding: { type: 'string', description: 'Encoding', default: 'utf-8' },
223
+ },
224
+ required: ['path', 'content'],
225
+ },
226
+ requiresApproval: true, // Requires HITL approval
227
+ idempotent: true,
228
+ async execute({ path, content, encoding = 'utf-8' }, context) {
229
+ const fs = await import('fs/promises');
230
+ await fs.writeFile(path, content, encoding);
231
+ return { path, bytesWritten: Buffer.byteLength(content, encoding) };
232
+ },
233
+ });
234
+
235
+ // Code execution tool
236
+ tools.push({
237
+ name: 'code_exec',
238
+ description: 'Execute code in a sandboxed environment',
239
+ category: 'code',
240
+ parameters: {
241
+ type: 'object',
242
+ properties: {
243
+ code: { type: 'string', description: 'Code to execute' },
244
+ language: { type: 'string', description: 'Language', default: 'javascript' },
245
+ timeout: { type: 'number', description: 'Timeout in ms', default: 30000 },
246
+ },
247
+ required: ['code'],
248
+ },
249
+ requiresApproval: true, // Requires HITL approval
250
+ idempotent: false,
251
+ async execute({ code, language = 'javascript', timeout = 30000 }, context) {
252
+ // This would integrate with a sandbox like Pyodide, QuickJS, etc.
253
+ return { output: 'Code execution not configured', language };
254
+ },
255
+ });
256
+
257
+ // API call tool
258
+ tools.push({
259
+ name: 'api_call',
260
+ description: 'Make an HTTP API call',
261
+ category: 'network',
262
+ parameters: {
263
+ type: 'object',
264
+ properties: {
265
+ url: { type: 'string', description: 'API URL' },
266
+ method: { type: 'string', description: 'HTTP method', default: 'GET' },
267
+ headers: { type: 'object', description: 'Headers' },
268
+ body: { type: 'object', description: 'Request body' },
269
+ timeout: { type: 'number', description: 'Timeout in ms', default: 30000 },
270
+ },
271
+ required: ['url'],
272
+ },
273
+ requiresApproval: true, // Requires HITL approval
274
+ idempotent: true,
275
+ async execute({ url, method = 'GET', headers = {}, body, timeout = 30000 }, context) {
276
+ const controller = new AbortController();
277
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
278
+
279
+ try {
280
+ const response = await fetch(url, {
281
+ method,
282
+ headers: { 'Content-Type': 'application/json', ...headers },
283
+ body: body ? JSON.stringify(body) : undefined,
284
+ signal: controller.signal,
285
+ });
286
+
287
+ clearTimeout(timeoutId);
288
+ const data = await response.json();
289
+ return { status: response.status, data };
290
+ } catch (error) {
291
+ clearTimeout(timeoutId);
292
+ throw error;
293
+ }
294
+ },
295
+ });
296
+
297
+ // Email send tool
298
+ tools.push({
299
+ name: 'send_email',
300
+ description: 'Send an email',
301
+ category: 'communication',
302
+ parameters: {
303
+ type: 'object',
304
+ properties: {
305
+ to: { type: 'string', description: 'Recipient email' },
306
+ subject: { type: 'string', description: 'Email subject' },
307
+ body: { type: 'string', description: 'Email body' },
308
+ html: { type: 'string', description: 'HTML body' },
309
+ },
310
+ required: ['to', 'subject', 'body'],
311
+ },
312
+ requiresApproval: true, // Requires HITL approval
313
+ idempotent: true,
314
+ async execute({ to, subject, body, html }, context) {
315
+ // This would integrate with actual email service
316
+ return { to, subject, sent: false, note: 'Email service not configured' };
317
+ },
318
+ });
319
+
320
+ // Database query tool
321
+ tools.push({
322
+ name: 'db_query',
323
+ description: 'Execute a database query',
324
+ category: 'data',
325
+ parameters: {
326
+ type: 'object',
327
+ properties: {
328
+ query: { type: 'string', description: 'SQL query' },
329
+ params: { type: 'array', description: 'Query parameters' },
330
+ },
331
+ required: ['query'],
332
+ },
333
+ requiresApproval: true, // Requires HITL approval
334
+ idempotent: false,
335
+ async execute({ query, params = [] }, context) {
336
+ // This would use the existing storage
337
+ return { query, results: [], note: 'DB query not configured' };
338
+ },
339
+ });
340
+
341
+ return tools;
342
+ }