@looopy-ai/core 1.1.5 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import { Observable } from 'rxjs';
2
+ import type { SkillRegistry } from '../skills';
2
3
  import type { MessageStore } from '../stores/messages/interfaces';
3
4
  import type { AgentState, AgentStore } from '../types/agent';
4
5
  import type { AuthContext } from '../types/context';
@@ -17,6 +18,7 @@ export interface AgentConfig {
17
18
  autoCompact?: boolean;
18
19
  maxMessages?: number;
19
20
  systemPrompt?: SystemPromptProp;
21
+ skillRegistry?: SkillRegistry;
20
22
  logger?: import('pino').Logger;
21
23
  }
22
24
  export interface GetMessagesOptions {
@@ -1,4 +1,4 @@
1
- import { catchError, concat, Observable, of, tap } from 'rxjs';
1
+ import { catchError, concat, filter, Observable, of, tap } from 'rxjs';
2
2
  import { createTaskStatusEvent } from '../events';
3
3
  import { addMessagesCompactedEvent, addMessagesLoadedEvent, completeAgentInitializeSpan, completeAgentTurnSpan, failAgentInitializeSpan, failAgentTurnSpan, setResumeAttributes, setTurnCountAttribute, startAgentInitializeSpan, startAgentTurnSpan, } from '../observability/spans';
4
4
  import { serializeError } from '../utils/error';
@@ -102,7 +102,7 @@ export class Agent {
102
102
  }
103
103
  if (this._state.status === 'error') {
104
104
  const error = new Error(`Cannot execute turn: Agent is in error state: ${this._state.error?.message}`);
105
- logger.error(error.message);
105
+ logger.error(this._state.error, 'Cannot execute turn due to agent error state');
106
106
  failAgentTurnSpan(rootSpan, error);
107
107
  return of(createTaskStatusEvent({
108
108
  contextId: this.config.contextId,
@@ -170,6 +170,7 @@ export class Agent {
170
170
  parentContext: turnContext,
171
171
  systemPrompt: this.config.systemPrompt,
172
172
  toolProviders: this.config.toolProviders,
173
+ skillRegistry: this.config.skillRegistry,
173
174
  logger: this.config.logger.child({ taskId, turnNumber }),
174
175
  turnNumber,
175
176
  }, {
@@ -210,10 +211,14 @@ export class Agent {
210
211
  await this.config.messageStore.append(this.config.contextId, [message]);
211
212
  break;
212
213
  }
214
+ case 'internal:tool-message':
215
+ logger.debug({ message: event.message }, 'Saving internal tool message to message store');
216
+ await this.config.messageStore.append(this.config.contextId, [event.message]);
217
+ break;
213
218
  default:
214
219
  break;
215
220
  }
216
- }));
221
+ }), filter((event) => event.kind !== 'internal:tool-message'));
217
222
  turnEvents$.subscribe({
218
223
  next: (event) => {
219
224
  observer.next(event);
@@ -27,7 +27,7 @@ export const runIteration = (context, config, history) => {
27
27
  taskId: context.taskId,
28
28
  ...event,
29
29
  })), finishLLMCallSpan);
30
- }), shareReplay());
30
+ }), shareReplay({ refCount: true }));
31
31
  const toolEvents$ = llmEvents$.pipe(filter((event) => event.kind === 'tool-call'), mergeMap((event) => runToolCall({
32
32
  ...context,
33
33
  logger: context.logger.child({ iteration: config.iterationNumber }),
package/dist/core/loop.js CHANGED
@@ -27,8 +27,20 @@ export const runLoop = (context, config, history) => {
27
27
  status: 'working',
28
28
  metadata: {},
29
29
  });
30
+ const initialMessages = [...history];
31
+ if (context.skillRegistry) {
32
+ const skills = context.skillRegistry.list();
33
+ if (skills.length > 0) {
34
+ const skillList = skills.map((s) => `- **${s.name}**: ${s.description}`).join('\n');
35
+ const skillMessage = {
36
+ role: 'system',
37
+ content: `You can learn new skills by using the 'learn_skill' tool. Available skills:\n\n${skillList}`,
38
+ };
39
+ initialMessages.unshift(skillMessage);
40
+ }
41
+ }
30
42
  const merged$ = recursiveMerge({
31
- messages: history,
43
+ messages: initialMessages,
32
44
  completed: false,
33
45
  iteration: 0,
34
46
  }, (state) => runIteration({ ...context, parentContext: loopContext }, {
@@ -37,7 +49,7 @@ export const runLoop = (context, config, history) => {
37
49
  }, state.messages), (state, { events }) => ({
38
50
  ...state,
39
51
  messages: [...state.messages, ...eventsToMessages(events)],
40
- }), (e) => e.kind === 'content-complete' && e.finishReason !== 'tool_calls').pipe(shareReplay());
52
+ }), (e) => e.kind === 'content-complete' && e.finishReason !== 'tool_calls').pipe(shareReplay({ refCount: true }));
41
53
  const finalSummary$ = merged$.pipe(reduce((last, e) => (e.kind === 'content-complete' ? e : last), null), mergeMap((last) => {
42
54
  if (!last)
43
55
  return EMPTY;
@@ -81,6 +93,9 @@ const eventsToMessages = (events) => {
81
93
  toolCallId: event.toolCallId,
82
94
  });
83
95
  break;
96
+ case 'internal:tool-message':
97
+ messages.push(event.message);
98
+ break;
84
99
  default:
85
100
  break;
86
101
  }
@@ -1,4 +1,4 @@
1
1
  import { type Observable } from 'rxjs';
2
- import type { ToolCallEvent, ToolExecutionEvent } from '../types/event';
2
+ import type { InternalToolMessageEvent, ToolCallEvent, ToolExecutionEvent } from '../types/event';
3
3
  import type { IterationContext } from './types';
4
- export declare const runToolCall: (context: IterationContext, toolCall: ToolCallEvent) => Observable<ToolExecutionEvent>;
4
+ export declare const runToolCall: (context: IterationContext, toolCall: ToolCallEvent) => Observable<ToolExecutionEvent | InternalToolMessageEvent>;
@@ -1,4 +1,4 @@
1
- import { concat, defer, mergeMap, of } from 'rxjs';
1
+ import { catchError, concat, defer, EMPTY, from, mergeMap, of, shareReplay, } from 'rxjs';
2
2
  import { startToolExecuteSpan } from '../observability/spans';
3
3
  export const runToolCall = (context, toolCall) => {
4
4
  const logger = context.logger.child({
@@ -29,34 +29,45 @@ export const runToolCall = (context, toolCall) => {
29
29
  timestamp: new Date().toISOString(),
30
30
  };
31
31
  const { tapFinish } = startToolExecuteSpan(context, toolCall);
32
- const toolResultEvents$ = defer(async () => {
32
+ const toolResults$ = defer(async () => {
33
33
  logger.trace({ providerName: provider.name }, 'Executing tool');
34
- try {
35
- const result = await provider.execute({
36
- id: toolCall.toolCallId,
37
- type: 'function',
38
- function: {
39
- name: toolCall.toolName,
40
- arguments: toolCall.arguments,
41
- },
42
- }, context);
43
- logger.trace({
44
- success: result.success,
45
- }, 'Tool execution complete');
46
- return result.success
47
- ? createToolCompleteEvent(context, toolCall, result.result)
48
- : createToolErrorEvent(context, toolCall, result.error || 'Unknown error');
34
+ return await provider.execute({
35
+ id: toolCall.toolCallId,
36
+ type: 'function',
37
+ function: {
38
+ name: toolCall.toolName,
39
+ arguments: toolCall.arguments,
40
+ },
41
+ }, context);
42
+ }).pipe(tapFinish, shareReplay({ refCount: true }));
43
+ const toolCompleteEvents$ = toolResults$.pipe(mergeMap((result) => {
44
+ if (result.success) {
45
+ logger.trace({ providerName: provider.name, success: true }, 'Tool execution complete');
46
+ return of(createToolCompleteEvent(context, toolCall, result.result));
49
47
  }
50
- catch (error) {
51
- const err = error instanceof Error ? error : new Error(String(error));
52
- logger.error({
53
- error: err.message,
54
- stack: err.stack,
55
- }, 'Tool execution failed');
56
- return createToolErrorEvent(context, toolCall, err.message);
48
+ else {
49
+ logger.trace({ providerName: provider.name, success: false }, 'Tool execution failed');
50
+ return of(createToolErrorEvent(context, toolCall, result.error || 'Unknown error'));
57
51
  }
58
- });
59
- return concat(of(toolStartEvent), toolResultEvents$).pipe(tapFinish);
52
+ }), catchError((error) => {
53
+ const err = error instanceof Error ? error : new Error(String(error));
54
+ logger.error({
55
+ providerName: provider.name,
56
+ error: err.message,
57
+ stack: err.stack,
58
+ }, 'Tool execution error');
59
+ return of(createToolErrorEvent(context, toolCall, err.message));
60
+ }));
61
+ const toolMessageEvents$ = toolResults$.pipe(mergeMap((result) => result.success && result.messages
62
+ ? from(result.messages?.map((message) => ({
63
+ kind: 'internal:tool-message',
64
+ message,
65
+ timestamp: new Date().toISOString(),
66
+ })))
67
+ : EMPTY), catchError(() => {
68
+ return EMPTY;
69
+ }));
70
+ return concat(of(toolStartEvent), toolCompleteEvents$, toolMessageEvents$);
60
71
  }).pipe(mergeMap((obs) => obs));
61
72
  };
62
73
  const createToolCompleteEvent = (context, toolCall, result) => ({
@@ -1,4 +1,5 @@
1
1
  import type pino from 'pino';
2
+ import type { SkillRegistry } from '../skills';
2
3
  import type { AuthContext } from '../types/context';
3
4
  import type { LLMProvider } from '../types/llm';
4
5
  import type { ToolProvider } from '../types/tools';
@@ -11,6 +12,7 @@ export type AgentContext = {
11
12
  toolProviders: ToolProvider[];
12
13
  logger: pino.Logger;
13
14
  systemPrompt?: SystemPromptProp;
15
+ skillRegistry?: SkillRegistry;
14
16
  skillPrompts?: Record<string, string>;
15
17
  metadata?: Record<string, unknown>;
16
18
  };
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export * from './events';
3
3
  export * from './observability';
4
4
  export * from './providers';
5
5
  export * from './server';
6
+ export * from './skills';
6
7
  export * from './stores';
7
8
  export * from './tools';
8
9
  export * from './types';
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export * from './events';
3
3
  export * from './observability';
4
4
  export * from './providers';
5
5
  export * from './server';
6
+ export * from './skills';
6
7
  export * from './stores';
7
8
  export * from './tools';
8
9
  export * from './types';
@@ -1,6 +1,6 @@
1
1
  import type { IterationContext } from '../../core/types';
2
- import type { ToolCallEvent, ToolExecutionEvent } from '../../types/event';
3
- import type { ToolCall } from '../../types/tools';
2
+ import type { ToolCallEvent } from '../../types/event';
3
+ import type { ToolCall, ToolResult } from '../../types/tools';
4
4
  export interface ToolExecutionSpanParams {
5
5
  agentId: string;
6
6
  taskId: string;
@@ -10,5 +10,5 @@ export interface ToolExecutionSpanParams {
10
10
  export declare const startToolExecuteSpan: (context: IterationContext, toolStart: ToolCallEvent) => {
11
11
  span: import("@opentelemetry/api").Span;
12
12
  traceContext: import("@opentelemetry/api").Context;
13
- tapFinish: import("rxjs").MonoTypeOperatorFunction<ToolExecutionEvent>;
13
+ tapFinish: import("rxjs").MonoTypeOperatorFunction<ToolResult>;
14
14
  };
@@ -19,19 +19,8 @@ export const startToolExecuteSpan = (context, toolStart) => {
19
19
  traceContext,
20
20
  tapFinish: tap({
21
21
  next: (event) => {
22
- if (event.kind === 'tool-complete') {
23
- if (event.success) {
24
- span.setAttribute('output', JSON.stringify(event.result));
25
- span.setStatus({ code: SpanStatusCode.OK });
26
- }
27
- else {
28
- span.setAttribute('output', event.error || 'Tool execution failed');
29
- span.setStatus({ code: SpanStatusCode.ERROR });
30
- if (event.error) {
31
- span.setAttribute('error.message', event.error);
32
- }
33
- }
34
- }
22
+ span.setAttribute('output', JSON.stringify(event.result));
23
+ span.setStatus({ code: SpanStatusCode.OK });
35
24
  },
36
25
  complete: () => span.end(),
37
26
  error: (err) => {
@@ -40,7 +40,7 @@ export class LiteLLMProvider {
40
40
  const rawStream$ = this.createSSEStream(request);
41
41
  const stream$ = (this.config.debugLogPath
42
42
  ? rawStream$.pipe(tap((chunk) => this.debugLogRawChunk(chunk)))
43
- : rawStream$).pipe(shareReplay());
43
+ : rawStream$).pipe(shareReplay({ refCount: true }));
44
44
  const choices$ = stream$.pipe(choices());
45
45
  const usage$ = stream$.pipe(usage());
46
46
  const { content, tags } = splitInlineXml(choices$.pipe(getContent()));
@@ -181,11 +181,11 @@ export class LiteLLMProvider {
181
181
  role: msg.role,
182
182
  content: msg.content,
183
183
  };
184
- if (msg.name)
184
+ if ('name' in msg && msg.name)
185
185
  baseMsg.name = msg.name;
186
- if (msg.toolCallId)
186
+ if (msg.role === 'tool' && msg.toolCallId)
187
187
  baseMsg.tool_call_id = msg.toolCallId;
188
- if (msg.toolCalls && msg.toolCalls.length > 0) {
188
+ if (msg.role === 'assistant' && msg.toolCalls && msg.toolCalls.length > 0) {
189
189
  baseMsg.tool_calls = msg.toolCalls.map((tc) => ({
190
190
  id: tc.id,
191
191
  type: tc.type,
@@ -0,0 +1 @@
1
+ export * from './registry';
@@ -0,0 +1 @@
1
+ export * from './registry';
@@ -0,0 +1,14 @@
1
+ import z from 'zod';
2
+ import type { Skill } from '../types';
3
+ export declare const learnSkillToolName = "learn_skill";
4
+ export declare const skill: (definition: Skill) => Skill;
5
+ export declare class SkillRegistry {
6
+ private skills;
7
+ constructor(skills?: Skill[]);
8
+ register(skill: Skill): void;
9
+ get(name: string): Skill | undefined;
10
+ list(): Skill[];
11
+ tool(): import("..").LocalToolDefinition<z.ZodObject<{
12
+ name: z.ZodString;
13
+ }, z.core.$strip>>;
14
+ }
@@ -0,0 +1,60 @@
1
+ import z from 'zod';
2
+ import { tool } from '../tools/local-tools';
3
+ export const learnSkillToolName = 'learn_skill';
4
+ const getInstruction = async (instruction) => {
5
+ if (typeof instruction === 'string') {
6
+ return instruction;
7
+ }
8
+ return await instruction();
9
+ };
10
+ export const skill = (definition) => {
11
+ return { ...definition };
12
+ };
13
+ export class SkillRegistry {
14
+ skills = {};
15
+ constructor(skills = []) {
16
+ skills.forEach((skill) => {
17
+ this.skills[skill.name] = skill;
18
+ });
19
+ }
20
+ register(skill) {
21
+ this.skills[skill.name] = skill;
22
+ }
23
+ get(name) {
24
+ return this.skills[name];
25
+ }
26
+ list() {
27
+ return Object.values(this.skills);
28
+ }
29
+ tool() {
30
+ return tool({
31
+ name: learnSkillToolName,
32
+ description: 'Learns a new skill from the available skill registry.',
33
+ schema: z.object({
34
+ name: z.string().describe('The name of the skill to learn.'),
35
+ }),
36
+ handler: async ({ name }) => {
37
+ const foundSkill = this.get(name);
38
+ if (!foundSkill) {
39
+ const availableSkills = this.list()
40
+ .map((s) => `'${s.name}'`)
41
+ .join(', ');
42
+ return {
43
+ success: false,
44
+ result: null,
45
+ error: `Skill '${name}' not found. Available skills are: ${availableSkills}`,
46
+ };
47
+ }
48
+ const systemMessage = {
49
+ role: 'system',
50
+ content: `You have learned the following skill:\n\n**${foundSkill.name}**\n${await getInstruction(foundSkill.instruction)}`,
51
+ };
52
+ return {
53
+ success: true,
54
+ result: `Successfully learned the '${name}' skill.`,
55
+ messages: [systemMessage],
56
+ };
57
+ },
58
+ });
59
+ }
60
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Message } from '../../types/message';
2
- export interface StoredMessage extends Message {
2
+ export type StoredMessage = Message & {
3
3
  id: string;
4
4
  contextId: string;
5
5
  index: number;
@@ -11,7 +11,7 @@ export interface StoredMessage extends Message {
11
11
  start: number;
12
12
  end: number;
13
13
  };
14
- }
14
+ };
15
15
  export interface MessageStore {
16
16
  append(contextId: string, messages: Message[]): Promise<void>;
17
17
  getRecent(contextId: string, options?: {
@@ -51,12 +51,15 @@ export function createArtifactTools(artifactStore, taskStateStore) {
51
51
  });
52
52
  await trackArtifactInState(context.taskId, params.artifactId, taskStateStore);
53
53
  return {
54
- artifactId: params.artifactId,
55
- type: 'file',
56
- status: 'building',
57
- message: params.override
58
- ? 'File artifact reset. Use append_file_chunk to add content.'
59
- : 'File artifact created. Use append_file_chunk to add content.',
54
+ success: true,
55
+ result: {
56
+ artifactId: params.artifactId,
57
+ type: 'file',
58
+ status: 'building',
59
+ message: params.override
60
+ ? 'File artifact reset. Use append_file_chunk to add content.'
61
+ : 'File artifact created. Use append_file_chunk to add content.',
62
+ },
60
63
  };
61
64
  },
62
65
  }),
@@ -77,12 +80,15 @@ export function createArtifactTools(artifactStore, taskStateStore) {
77
80
  isLastChunk: params.isLastChunk,
78
81
  });
79
82
  return {
80
- artifactId: params.artifactId,
81
- chunkAdded: true,
82
- complete: params.isLastChunk,
83
- message: params.isLastChunk
84
- ? 'Final chunk appended. Artifact is complete.'
85
- : 'Chunk appended successfully.',
83
+ success: true,
84
+ result: {
85
+ artifactId: params.artifactId,
86
+ chunkAdded: true,
87
+ complete: params.isLastChunk,
88
+ message: params.isLastChunk
89
+ ? 'Final chunk appended. Artifact is complete.'
90
+ : 'Chunk appended successfully.',
91
+ },
86
92
  };
87
93
  },
88
94
  }),
@@ -95,8 +101,11 @@ export function createArtifactTools(artifactStore, taskStateStore) {
95
101
  handler: async (params, context) => {
96
102
  const content = await scheduledStore.getFileContent(context.contextId, params.artifactId);
97
103
  return {
98
- artifactId: params.artifactId,
99
- content,
104
+ success: true,
105
+ result: {
106
+ artifactId: params.artifactId,
107
+ content,
108
+ },
100
109
  };
101
110
  },
102
111
  }),
@@ -126,12 +135,15 @@ export function createArtifactTools(artifactStore, taskStateStore) {
126
135
  await scheduledStore.writeData(context.contextId, params.artifactId, params.data);
127
136
  await trackArtifactInState(context.taskId, params.artifactId, taskStateStore);
128
137
  return {
129
- artifactId: params.artifactId,
130
- type: 'data',
131
- status: 'complete',
132
- message: params.override
133
- ? 'Data artifact reset successfully.'
134
- : 'Data artifact created successfully.',
138
+ success: true,
139
+ result: {
140
+ artifactId: params.artifactId,
141
+ type: 'data',
142
+ status: 'complete',
143
+ message: params.override
144
+ ? 'Data artifact reset successfully.'
145
+ : 'Data artifact created successfully.',
146
+ },
135
147
  };
136
148
  },
137
149
  }),
@@ -145,10 +157,13 @@ export function createArtifactTools(artifactStore, taskStateStore) {
145
157
  handler: async (params, context) => {
146
158
  await scheduledStore.writeData(context.contextId, params.artifactId, params.data);
147
159
  return {
148
- artifactId: params.artifactId,
149
- type: 'data',
150
- status: 'complete',
151
- message: 'Data artifact updated successfully.',
160
+ success: true,
161
+ result: {
162
+ artifactId: params.artifactId,
163
+ type: 'data',
164
+ status: 'complete',
165
+ message: 'Data artifact updated successfully.',
166
+ },
152
167
  };
153
168
  },
154
169
  }),
@@ -161,8 +176,11 @@ export function createArtifactTools(artifactStore, taskStateStore) {
161
176
  handler: async (params, context) => {
162
177
  const data = await scheduledStore.getDataContent(context.contextId, params.artifactId);
163
178
  return {
164
- artifactId: params.artifactId,
165
- data,
179
+ success: true,
180
+ result: {
181
+ artifactId: params.artifactId,
182
+ data,
183
+ },
166
184
  };
167
185
  },
168
186
  }),
@@ -175,8 +193,11 @@ export function createArtifactTools(artifactStore, taskStateStore) {
175
193
  handler: async (params, context) => {
176
194
  const data = await scheduledStore.getDataContent(context.contextId, params.artifactId);
177
195
  return {
178
- artifactId: params.artifactId,
179
- data,
196
+ success: true,
197
+ result: {
198
+ artifactId: params.artifactId,
199
+ data,
200
+ },
180
201
  };
181
202
  },
182
203
  }),
@@ -212,12 +233,15 @@ export function createArtifactTools(artifactStore, taskStateStore) {
212
233
  });
213
234
  await trackArtifactInState(context.taskId, params.artifactId, taskStateStore);
214
235
  return {
215
- artifactId: params.artifactId,
216
- type: 'dataset',
217
- status: 'building',
218
- message: params.override
219
- ? 'Dataset artifact reset. Use append_dataset_row(s) to add data.'
220
- : 'Dataset artifact created. Use append_dataset_row(s) to add data.',
236
+ success: true,
237
+ result: {
238
+ artifactId: params.artifactId,
239
+ type: 'dataset',
240
+ status: 'building',
241
+ message: params.override
242
+ ? 'Dataset artifact reset. Use append_dataset_row(s) to add data.'
243
+ : 'Dataset artifact created. Use append_dataset_row(s) to add data.',
244
+ },
221
245
  };
222
246
  },
223
247
  }),
@@ -231,9 +255,12 @@ export function createArtifactTools(artifactStore, taskStateStore) {
231
255
  handler: async (params, context) => {
232
256
  await scheduledStore.appendDatasetBatch(context.contextId, params.artifactId, [params.row]);
233
257
  return {
234
- artifactId: params.artifactId,
235
- rowAdded: true,
236
- message: 'Row appended to dataset.',
258
+ success: true,
259
+ result: {
260
+ artifactId: params.artifactId,
261
+ rowAdded: true,
262
+ message: 'Row appended to dataset.',
263
+ },
237
264
  };
238
265
  },
239
266
  }),
@@ -250,9 +277,12 @@ export function createArtifactTools(artifactStore, taskStateStore) {
250
277
  isLastBatch: params.isLastBatch,
251
278
  });
252
279
  return {
253
- artifactId: params.artifactId,
254
- rowsAdded: params.rows.length,
255
- message: `${params.rows.length} rows appended to dataset.`,
280
+ success: true,
281
+ result: {
282
+ artifactId: params.artifactId,
283
+ rowsAdded: params.rows.length,
284
+ message: `${params.rows.length} rows appended to dataset.`,
285
+ },
256
286
  };
257
287
  },
258
288
  }),
@@ -265,9 +295,12 @@ export function createArtifactTools(artifactStore, taskStateStore) {
265
295
  handler: async (params, context) => {
266
296
  const rows = await scheduledStore.getDatasetRows(context.contextId, params.artifactId);
267
297
  return {
268
- artifactId: params.artifactId,
269
- rows,
270
- totalRows: rows.length,
298
+ success: true,
299
+ result: {
300
+ artifactId: params.artifactId,
301
+ rows,
302
+ totalRows: rows.length,
303
+ },
271
304
  };
272
305
  },
273
306
  }),
@@ -282,15 +315,18 @@ export function createArtifactTools(artifactStore, taskStateStore) {
282
315
  const artifacts = await Promise.all(artifactIds.map((id) => scheduledStore.getArtifact(context.contextId, id)));
283
316
  const validArtifacts = artifacts.filter((a) => a !== null);
284
317
  return {
285
- artifacts: validArtifacts.map((a) => ({
286
- artifactId: a.artifactId,
287
- type: a.type,
288
- name: a.name,
289
- taskId: a.taskId,
290
- contextId: a.contextId,
291
- createdAt: a.createdAt,
292
- })),
293
- totalCount: validArtifacts.length,
318
+ success: true,
319
+ result: {
320
+ artifacts: validArtifacts.map((a) => ({
321
+ artifactId: a.artifactId,
322
+ type: a.type,
323
+ name: a.name,
324
+ taskId: a.taskId,
325
+ contextId: a.contextId,
326
+ createdAt: a.createdAt,
327
+ })),
328
+ totalCount: validArtifacts.length,
329
+ },
294
330
  };
295
331
  },
296
332
  }),
@@ -306,25 +342,28 @@ export function createArtifactTools(artifactStore, taskStateStore) {
306
342
  throw new Error(`Artifact not found: ${params.artifactId}`);
307
343
  }
308
344
  return {
309
- artifactId: artifact.artifactId,
310
- type: artifact.type,
311
- taskId: artifact.taskId,
312
- contextId: artifact.contextId,
313
- name: artifact.name,
314
- description: artifact.description,
315
- status: artifact.status,
316
- createdAt: artifact.createdAt,
317
- updatedAt: artifact.updatedAt,
318
- ...(artifact.type === 'file' && {
319
- mimeType: artifact.mimeType,
320
- encoding: artifact.encoding,
321
- totalChunks: artifact.totalChunks,
322
- totalSize: artifact.totalSize,
323
- }),
324
- ...(artifact.type === 'dataset' && {
325
- totalRows: artifact.totalSize,
326
- schema: artifact.schema,
327
- }),
345
+ success: true,
346
+ result: {
347
+ artifactId: artifact.artifactId,
348
+ type: artifact.type,
349
+ taskId: artifact.taskId,
350
+ contextId: artifact.contextId,
351
+ name: artifact.name,
352
+ description: artifact.description,
353
+ status: artifact.status,
354
+ createdAt: artifact.createdAt,
355
+ updatedAt: artifact.updatedAt,
356
+ ...(artifact.type === 'file' && {
357
+ mimeType: artifact.mimeType,
358
+ encoding: artifact.encoding,
359
+ totalChunks: artifact.totalChunks,
360
+ totalSize: artifact.totalSize,
361
+ }),
362
+ ...(artifact.type === 'dataset' && {
363
+ totalRows: artifact.totalSize,
364
+ schema: artifact.schema,
365
+ }),
366
+ },
328
367
  };
329
368
  },
330
369
  }),
@@ -337,9 +376,12 @@ export function createArtifactTools(artifactStore, taskStateStore) {
337
376
  handler: async (params, context) => {
338
377
  await scheduledStore.deleteArtifact(context.contextId, params.artifactId);
339
378
  return {
340
- artifactId: params.artifactId,
341
- deleted: true,
342
- message: 'Artifact deleted successfully.',
379
+ success: true,
380
+ result: {
381
+ artifactId: params.artifactId,
382
+ deleted: true,
383
+ message: 'Artifact deleted successfully.',
384
+ },
343
385
  };
344
386
  },
345
387
  }),
@@ -1,7 +1,8 @@
1
1
  import { z } from 'zod';
2
2
  import type { ExecutionContext } from '../types/context';
3
- import type { ToolProvider } from '../types/tools';
4
- export type ToolHandler<TParams> = (params: TParams, context: ExecutionContext) => Promise<unknown> | unknown;
3
+ import type { ToolProvider, ToolResult } from '../types/tools';
4
+ type InternalToolResult = Omit<ToolResult, 'toolCallId' | 'toolName'>;
5
+ export type ToolHandler<TParams> = (params: TParams, context: ExecutionContext) => Promise<InternalToolResult> | InternalToolResult;
5
6
  export interface LocalToolDefinition<TSchema extends z.ZodObject> {
6
7
  name: string;
7
8
  description: string;
@@ -11,3 +12,4 @@ export interface LocalToolDefinition<TSchema extends z.ZodObject> {
11
12
  }
12
13
  export declare function tool<TSchema extends z.ZodObject>(definition: LocalToolDefinition<TSchema>): LocalToolDefinition<TSchema>;
13
14
  export declare function localTools(tools: LocalToolDefinition<z.ZodObject>[]): ToolProvider;
15
+ export {};
@@ -55,8 +55,10 @@ export function localTools(tools) {
55
55
  return {
56
56
  toolCallId: toolCall.id,
57
57
  toolName: toolCall.function.name,
58
- success: true,
59
- result,
58
+ success: result.success,
59
+ error: result.error,
60
+ result: result.result,
61
+ messages: result.messages,
60
62
  };
61
63
  }
62
64
  catch (error) {
@@ -1,3 +1,4 @@
1
+ import type { Message, SystemMessage } from './message';
1
2
  import type { ToolCall } from './tools';
2
3
  export type TaskStatus = 'working' | 'waiting-input' | 'waiting-auth' | 'waiting-subtask' | 'completed' | 'failed' | 'canceled';
3
4
  export type ThoughtVerbosity = 'brief' | 'normal' | 'detailed';
@@ -306,6 +307,13 @@ export interface InternalLLMCallEvent {
306
307
  toolCount: number;
307
308
  timestamp: string;
308
309
  }
310
+ export interface InternalToolMessageEvent {
311
+ kind: 'internal:tool-message';
312
+ contextId: string;
313
+ taskId: string;
314
+ message: SystemMessage;
315
+ timestamp: string;
316
+ }
309
317
  export interface InternalCheckpointEvent {
310
318
  kind: 'internal:checkpoint';
311
319
  contextId: string;
@@ -331,7 +339,7 @@ export interface InternalToolCompleteEvent {
331
339
  error?: string;
332
340
  timestamp: string;
333
341
  }
334
- export type InternalDebugEvent = InternalLLMCallEvent | InternalCheckpointEvent | InternalToolStartEvent | InternalToolCompleteEvent | InternalThoughtProcessEvent;
342
+ export type InternalDebugEvent = InternalLLMCallEvent | InternalToolMessageEvent | InternalCheckpointEvent | InternalToolStartEvent | InternalToolCompleteEvent | InternalThoughtProcessEvent;
335
343
  export interface LLMUsageEvent {
336
344
  kind: 'llm-usage';
337
345
  contextId: string;
@@ -347,7 +355,14 @@ export interface LLMUsageEvent {
347
355
  timestamp: string;
348
356
  }
349
357
  export type UsageEvent = LLMUsageEvent;
350
- export type AnyEvent = TaskLifecycleEvent | ContentStreamingEvent | ToolExecutionEvent | InputRequestEvent | AuthenticationEvent | ArtifactEvent | SubAgentEvent | ThoughtStreamEvent | InternalDebugEvent | UsageEvent;
358
+ export interface MessageEvent {
359
+ kind: 'message';
360
+ contextId: string;
361
+ taskId: string;
362
+ message: Message;
363
+ timestamp: string;
364
+ }
365
+ export type AnyEvent = TaskLifecycleEvent | ContentStreamingEvent | ToolExecutionEvent | InputRequestEvent | AuthenticationEvent | ArtifactEvent | SubAgentEvent | ThoughtStreamEvent | InternalDebugEvent | UsageEvent | MessageEvent;
351
366
  export type LLMEvent<T> = Omit<T, 'contextId' | 'taskId'>;
352
367
  export type ExternalEvent = Exclude<AnyEvent, InternalDebugEvent>;
353
368
  export type DebugEvent = InternalDebugEvent;
@@ -5,5 +5,6 @@ export * from './context';
5
5
  export * from './event';
6
6
  export * from './llm';
7
7
  export * from './message';
8
+ export * from './skills';
8
9
  export * from './state';
9
10
  export * from './tools';
@@ -5,5 +5,6 @@ export * from './context';
5
5
  export * from './event';
6
6
  export * from './llm';
7
7
  export * from './message';
8
+ export * from './skills';
8
9
  export * from './state';
9
10
  export * from './tools';
@@ -1,9 +1,24 @@
1
1
  import type { ToolCall } from './tools';
2
- export interface Message {
3
- role: 'system' | 'user' | 'assistant' | 'tool';
2
+ export type SystemMessage = {
3
+ role: 'system';
4
+ content: string;
5
+ name?: string;
6
+ };
7
+ export type UserMessage = {
8
+ role: 'user';
9
+ content: string;
10
+ name?: string;
11
+ };
12
+ export type AssistantMessage = {
13
+ role: 'assistant';
4
14
  content: string;
5
15
  name?: string;
6
- toolCallId?: string;
7
16
  toolCalls?: ToolCall[];
8
- contentDelta?: string;
9
- }
17
+ };
18
+ export type ToolMessage = {
19
+ role: 'tool';
20
+ content: string;
21
+ name?: string;
22
+ toolCallId: string;
23
+ };
24
+ export type Message = SystemMessage | UserMessage | AssistantMessage | ToolMessage;
@@ -0,0 +1,8 @@
1
+ export interface Skill {
2
+ name: string;
3
+ description: string;
4
+ instruction: string | (() => Promise<string>);
5
+ }
6
+ export interface SkillRegistration {
7
+ [key: string]: Skill;
8
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import type { ExecutionContext } from './context';
3
+ import type { SystemMessage } from './message';
3
4
  export interface ToolCall {
4
5
  id: string;
5
6
  type: 'function';
@@ -14,6 +15,7 @@ export interface ToolResult {
14
15
  success: boolean;
15
16
  result: unknown;
16
17
  error?: string;
18
+ messages?: SystemMessage[];
17
19
  }
18
20
  export declare const JsonSchemaPropertySchema: z.ZodType<unknown>;
19
21
  export type JsonSchemaProperty = z.infer<typeof JsonSchemaPropertySchema>;
@@ -1,7 +1,7 @@
1
1
  export type SystemPrompt = {
2
2
  prompt: string;
3
3
  name?: string;
4
- version?: string;
4
+ version?: number;
5
5
  };
6
6
  export type SystemPromptProp = string | SystemPrompt | (() => Promise<SystemPrompt> | SystemPrompt);
7
7
  export declare const getSystemPrompt: (systemPrompt?: SystemPromptProp) => Promise<SystemPrompt | undefined>;
@@ -3,7 +3,7 @@ export function recursiveMerge(initial, eventsFor, next, isStop) {
3
3
  const seed = {
4
4
  state: initial,
5
5
  iteration: 0,
6
- events$: eventsFor({ ...initial, iteration: 0 }).pipe(shareReplay()),
6
+ events$: eventsFor({ ...initial, iteration: 0 }).pipe(shareReplay({ refCount: true })),
7
7
  };
8
8
  const iterations$ = of(seed).pipe(expand(({ state, iteration, events$ }) => events$.pipe(reduce((acc, e) => {
9
9
  acc.events.push(e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@looopy-ai/core",
3
- "version": "1.1.5",
3
+ "version": "1.2.0",
4
4
  "description": "RxJS-based AI agent framework",
5
5
  "keywords": [
6
6
  "agent",