@looopy-ai/core 1.2.0 → 2.0.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 (37) hide show
  1. package/dist/core/agent.d.ts +2 -2
  2. package/dist/core/iteration.d.ts +2 -2
  3. package/dist/core/iteration.js +17 -12
  4. package/dist/core/loop.d.ts +2 -2
  5. package/dist/core/loop.js +1 -13
  6. package/dist/core/tools.d.ts +2 -2
  7. package/dist/core/tools.js +37 -59
  8. package/dist/core/types.d.ts +0 -1
  9. package/dist/events/utils.d.ts +25 -25
  10. package/dist/observability/spans/agent-turn.d.ts +2 -2
  11. package/dist/observability/spans/iteration.d.ts +2 -2
  12. package/dist/observability/spans/llm-call.d.ts +2 -1
  13. package/dist/observability/spans/llm-call.js +2 -1
  14. package/dist/observability/spans/loop.d.ts +2 -2
  15. package/dist/observability/spans/tool.d.ts +3 -3
  16. package/dist/observability/spans/tool.js +20 -3
  17. package/dist/providers/litellm-provider.d.ts +2 -2
  18. package/dist/server/event-buffer.d.ts +3 -3
  19. package/dist/server/event-router.d.ts +4 -4
  20. package/dist/server/sse.d.ts +3 -3
  21. package/dist/skills/registry.js +1 -0
  22. package/dist/stores/artifacts/internal-event-artifact-store.d.ts +2 -2
  23. package/dist/tools/agent-tool-provider.d.ts +36 -0
  24. package/dist/tools/agent-tool-provider.js +156 -0
  25. package/dist/tools/client-tool-provider.d.ts +1 -1
  26. package/dist/tools/client-tool-provider.js +31 -26
  27. package/dist/tools/index.d.ts +2 -0
  28. package/dist/tools/index.js +2 -0
  29. package/dist/tools/local-tools.js +4 -2
  30. package/dist/tools/mcp-tool-provider.d.ts +2 -3
  31. package/dist/tools/mcp-tool-provider.js +36 -35
  32. package/dist/tools/tool-result-events.d.ts +6 -0
  33. package/dist/tools/tool-result-events.js +33 -0
  34. package/dist/types/event.d.ts +5 -53
  35. package/dist/types/llm.d.ts +2 -2
  36. package/dist/types/tools.d.ts +3 -2
  37. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import type { AnyEvent } from '../types/event';
1
+ import type { ContextAnyEvent } from '../types/event';
2
2
  import { EventBuffer, type EventBufferConfig } from './event-buffer';
3
3
  import { EventRouter, type Subscriber, type SubscriptionConfig } from './event-router';
4
4
  export interface SSEResponse {
@@ -25,7 +25,7 @@ export declare class SSEConnection implements Subscriber {
25
25
  private closed;
26
26
  private lastEventId?;
27
27
  constructor(id: string, connectionConfig: SSEConnectionConfig);
28
- send(event: AnyEvent, eventId: string): void;
28
+ send(event: ContextAnyEvent, eventId: string): void;
29
29
  private sendHeartbeat;
30
30
  private startHeartbeat;
31
31
  private stopHeartbeat;
@@ -48,7 +48,7 @@ export declare class SSEServer {
48
48
  private heartbeatInterval;
49
49
  constructor(config?: SSEServerConfig);
50
50
  subscribe(response: SSEResponse, config: SubscriptionConfig, lastEventId?: string): SSEConnection;
51
- emit(contextId: string, event: AnyEvent): number;
51
+ emit(contextId: string, event: ContextAnyEvent): number;
52
52
  getSubscriberCount(contextId: string): number;
53
53
  getActiveContexts(): string[];
54
54
  getStats(): {
@@ -29,6 +29,7 @@ export class SkillRegistry {
29
29
  tool() {
30
30
  return tool({
31
31
  name: learnSkillToolName,
32
+ icon: 'lucide:graduation-cap',
32
33
  description: 'Learns a new skill from the available skill registry.',
33
34
  schema: z.object({
34
35
  name: z.string().describe('The name of the skill to learn.'),
@@ -1,7 +1,7 @@
1
1
  import type { ArtifactStore, DatasetSchema, StoredArtifact } from '../../types/artifact';
2
- import type { AnyEvent } from '../../types/event';
2
+ import type { ContextAnyEvent } from '../../types/event';
3
3
  export interface InternalEventEmitter {
4
- emit(event: AnyEvent): void;
4
+ emit(event: ContextAnyEvent): void;
5
5
  }
6
6
  export interface InternalEventArtifactStoreConfig {
7
7
  delegate: ArtifactStore;
@@ -0,0 +1,36 @@
1
+ import { Observable } from 'rxjs';
2
+ import z from 'zod';
3
+ import type { ContextAnyEvent, ExecutionContext } from '../types';
4
+ import type { ToolCall, ToolDefinition, ToolProvider } from '../types/tools';
5
+ export type HeaderFactory = (context?: ExecutionContext) => Promise<Record<string, string>>;
6
+ declare const cardSchema: z.ZodObject<{
7
+ name: z.ZodString;
8
+ description: z.ZodOptional<z.ZodString>;
9
+ url: z.ZodURL;
10
+ icon: z.ZodOptional<z.ZodString>;
11
+ skills: z.ZodOptional<z.ZodArray<z.ZodObject<{
12
+ name: z.ZodString;
13
+ description: z.ZodOptional<z.ZodString>;
14
+ }, z.core.$strip>>>;
15
+ auth: z.ZodOptional<z.ZodObject<{
16
+ issuer: z.ZodString;
17
+ audience: z.ZodString;
18
+ scopes: z.ZodArray<z.ZodString>;
19
+ }, z.core.$strip>>;
20
+ }, z.core.$strip>;
21
+ type AgentCard = z.infer<typeof cardSchema>;
22
+ export declare class AgentToolProvider implements ToolProvider {
23
+ readonly card: AgentCard;
24
+ readonly getHeaders?: HeaderFactory | undefined;
25
+ static fromUrl: (cardUrl: string, getHeaders?: HeaderFactory) => Promise<AgentToolProvider>;
26
+ static from: (card: AgentCard, getHeaders?: HeaderFactory) => AgentToolProvider;
27
+ private readonly agentName;
28
+ readonly name: string;
29
+ private readonly tools;
30
+ private readonly logger;
31
+ constructor(card: AgentCard, getHeaders?: HeaderFactory | undefined);
32
+ getTool(toolName: string): Promise<ToolDefinition | undefined>;
33
+ getTools(): Promise<ToolDefinition[]>;
34
+ execute(toolCall: ToolCall, context: ExecutionContext): Observable<ContextAnyEvent>;
35
+ }
36
+ export {};
@@ -0,0 +1,156 @@
1
+ import { consumeSSEStream } from '@geee-be/sse-stream-parser';
2
+ import { Observable } from 'rxjs';
3
+ import z from 'zod';
4
+ import { getLogger } from '../core';
5
+ import { toolErrorEvent } from './tool-result-events';
6
+ const cardSchema = z.object({
7
+ name: z.string(),
8
+ description: z.string().optional(),
9
+ url: z.url(),
10
+ icon: z.string().optional(),
11
+ skills: z
12
+ .array(z.object({
13
+ name: z.string(),
14
+ description: z.string().optional(),
15
+ }))
16
+ .optional(),
17
+ auth: z
18
+ .object({
19
+ issuer: z.string(),
20
+ audience: z.string(),
21
+ scopes: z.array(z.string()),
22
+ })
23
+ .optional(),
24
+ });
25
+ const safeName = (name) => name.replace(/[^a-zA-Z0-9-]+/g, '-').toLowerCase();
26
+ export class AgentToolProvider {
27
+ card;
28
+ getHeaders;
29
+ static fromUrl = (cardUrl, getHeaders) => {
30
+ return fetch(cardUrl)
31
+ .then((response) => response.json())
32
+ .then((card) => {
33
+ return AgentToolProvider.from(card, getHeaders);
34
+ });
35
+ };
36
+ static from = (card, getHeaders) => {
37
+ const parsed = cardSchema.parse(card);
38
+ return new AgentToolProvider(parsed, getHeaders);
39
+ };
40
+ agentName;
41
+ name;
42
+ tools;
43
+ logger;
44
+ constructor(card, getHeaders) {
45
+ this.card = card;
46
+ this.getHeaders = getHeaders;
47
+ this.agentName = safeName(card.name);
48
+ this.name = `agent__${this.agentName}`;
49
+ this.logger = getLogger({ component: 'agent-tool-provider', agentName: this.agentName });
50
+ this.tools = [
51
+ {
52
+ name: `${this.name}__invoke`,
53
+ description: `Invoke the ${card.name} agent.\n\n${card.description}` ||
54
+ `Invoke the ${card.name} agent`,
55
+ icon: card.icon,
56
+ parameters: {
57
+ type: 'object',
58
+ properties: {
59
+ prompt: {
60
+ type: 'string',
61
+ description: 'The prompt to send to the agent. This will call or invoke the agent sending this prompt as the input. This will start or continue a conversation "turn" with this agent. Example: "What is the weather today?"',
62
+ },
63
+ },
64
+ additionalProperties: false,
65
+ },
66
+ },
67
+ ];
68
+ }
69
+ getTool(toolName) {
70
+ const tool = this.tools.find((t) => t.name === toolName);
71
+ return Promise.resolve(tool);
72
+ }
73
+ getTools() {
74
+ return Promise.resolve(this.tools);
75
+ }
76
+ execute(toolCall, context) {
77
+ this.logger.debug({ toolCallId: toolCall.id, toolName: toolCall.function.name }, 'Executing agent tool call');
78
+ return new Observable((subscriber) => {
79
+ const abortController = new AbortController();
80
+ const run = async () => {
81
+ const tool = await this.getTool(toolCall.function.name);
82
+ if (!tool) {
83
+ this.logger.error({ toolName: toolCall.function.name }, 'Tool not found');
84
+ subscriber.next(toolErrorEvent(context, toolCall, `Tool not found: ${toolCall.function.name}`));
85
+ subscriber.complete();
86
+ return;
87
+ }
88
+ const prompt = toolCall.function.arguments.prompt;
89
+ if (!prompt || typeof prompt !== 'string') {
90
+ this.logger.error('Invalid tool call arguments');
91
+ subscriber.next(toolErrorEvent(context, toolCall, 'Tool argument must include "prompt" and it must be a string'));
92
+ subscriber.complete();
93
+ return;
94
+ }
95
+ const res = await fetch(`${this.card.url}/invocations?qualifier=DEFAULT`, {
96
+ method: 'POST',
97
+ headers: {
98
+ Accept: 'text/event-stream',
99
+ 'X-Amzn-Bedrock-AgentCore-Runtime-Session-Id': context.contextId,
100
+ ...(await this.getHeaders?.(context)),
101
+ },
102
+ body: JSON.stringify({ prompt }),
103
+ signal: abortController.signal,
104
+ });
105
+ if (!res.ok) {
106
+ this.logger.error({ status: res.status, statusText: res.statusText }, 'Agent call failed');
107
+ subscriber.next(toolErrorEvent(context, toolCall, `Agent endpoint responded with ${res.status} ${res.statusText}`));
108
+ subscriber.complete();
109
+ return;
110
+ }
111
+ const body = res.body;
112
+ if (!body) {
113
+ this.logger.error('Agent response has no body');
114
+ subscriber.next(toolErrorEvent(context, toolCall, 'Agent returned no response body'));
115
+ subscriber.complete();
116
+ return;
117
+ }
118
+ await consumeSSEStream(body, (e) => {
119
+ if (subscriber.closed)
120
+ return;
121
+ subscriber.next({
122
+ kind: e.event,
123
+ parentTaskId: context.taskId,
124
+ ...JSON.parse(e.data),
125
+ });
126
+ this.logger.debug({ event: e.event }, 'Received SSE event');
127
+ }, () => {
128
+ if (!subscriber.closed) {
129
+ const toolCompleteEvent = {
130
+ kind: 'tool-complete',
131
+ contextId: context.contextId,
132
+ taskId: context.taskId,
133
+ toolCallId: toolCall.id,
134
+ toolName: toolCall.function.name,
135
+ success: true,
136
+ result: 'Complete',
137
+ timestamp: new Date().toISOString(),
138
+ };
139
+ subscriber.next(toolCompleteEvent);
140
+ subscriber.complete();
141
+ this.logger.debug('Tool execution complete');
142
+ }
143
+ });
144
+ };
145
+ run().catch((err) => {
146
+ this.logger.error({ err }, 'Tool execution error');
147
+ if (!subscriber.closed) {
148
+ subscriber.error(err);
149
+ }
150
+ });
151
+ return () => {
152
+ abortController.abort();
153
+ };
154
+ });
155
+ }
156
+ }
@@ -11,7 +11,7 @@ export declare class ClientToolProvider implements ToolProvider {
11
11
  private readonly onInputRequired;
12
12
  constructor(config: ClientToolConfig);
13
13
  getTools(): Promise<ToolDefinition[]>;
14
- execute(toolCall: ToolCall, context: ExecutionContext): Promise<ToolResult>;
14
+ execute(toolCall: ToolCall, context: ExecutionContext): import("rxjs").Observable<import("..").ContextAnyEvent | import("..").ContextEvent<import("..").ToolCompleteEvent>>;
15
15
  getTool(name: string): Promise<ToolDefinition | undefined>;
16
16
  validateToolArguments(toolCall: ToolCall): Promise<{
17
17
  valid: boolean;
@@ -1,4 +1,6 @@
1
+ import { catchError, defer, mergeMap, of } from 'rxjs';
1
2
  import { validateToolDefinitions, } from '../types/tools';
3
+ import { toolErrorEvent, toolResultToEvents } from './tool-result-events';
2
4
  export class ClientToolProvider {
3
5
  name = 'client-tool-provider';
4
6
  tools;
@@ -25,39 +27,42 @@ export class ClientToolProvider {
25
27
  async getTools() {
26
28
  return [...this.tools];
27
29
  }
28
- async execute(toolCall, context) {
29
- const tool = await this.getTool(toolCall.function.name);
30
- if (!tool) {
31
- return {
32
- toolCallId: toolCall.id,
33
- toolName: toolCall.function.name,
34
- success: false,
35
- result: null,
36
- error: `Tool ${toolCall.function.name} not found in client tools`,
37
- };
38
- }
39
- try {
40
- if (typeof toolCall.function.arguments !== 'object' || toolCall.function.arguments === null) {
30
+ execute(toolCall, context) {
31
+ return defer(async () => {
32
+ const tool = await this.getTool(toolCall.function.name);
33
+ if (!tool) {
41
34
  return {
42
35
  toolCallId: toolCall.id,
43
36
  toolName: toolCall.function.name,
44
37
  success: false,
45
38
  result: null,
46
- error: `Invalid tool arguments: must be an object.`,
39
+ error: `Tool ${toolCall.function.name} not found in client tools`,
47
40
  };
48
41
  }
49
- const result = await this.onInputRequired(toolCall, context);
50
- return result;
51
- }
52
- catch (error) {
53
- return {
54
- toolCallId: toolCall.id,
55
- toolName: toolCall.function.name,
56
- success: false,
57
- result: null,
58
- error: error instanceof Error ? error.message : String(error),
59
- };
60
- }
42
+ try {
43
+ if (typeof toolCall.function.arguments !== 'object' ||
44
+ toolCall.function.arguments === null) {
45
+ return {
46
+ toolCallId: toolCall.id,
47
+ toolName: toolCall.function.name,
48
+ success: false,
49
+ result: null,
50
+ error: `Invalid tool arguments: must be an object.`,
51
+ };
52
+ }
53
+ const result = await this.onInputRequired(toolCall, context);
54
+ return result;
55
+ }
56
+ catch (error) {
57
+ return {
58
+ toolCallId: toolCall.id,
59
+ toolName: toolCall.function.name,
60
+ success: false,
61
+ result: null,
62
+ error: error instanceof Error ? error.message : String(error),
63
+ };
64
+ }
65
+ }).pipe(mergeMap((result) => toolResultToEvents(context, toolCall, result)), catchError((error) => of(toolErrorEvent(context, toolCall, error instanceof Error ? error.message : String(error)))));
61
66
  }
62
67
  async getTool(name) {
63
68
  return this.tools.find((t) => t.name === name);
@@ -1,4 +1,6 @@
1
+ export * from './agent-tool-provider';
1
2
  export * from './artifact-tools';
2
3
  export * from './client-tool-provider';
3
4
  export * from './local-tools';
4
5
  export * from './mcp-tool-provider';
6
+ export * from './tool-result-events';
@@ -1,4 +1,6 @@
1
+ export * from './agent-tool-provider';
1
2
  export * from './artifact-tools';
2
3
  export * from './client-tool-provider';
3
4
  export * from './local-tools';
4
5
  export * from './mcp-tool-provider';
6
+ export * from './tool-result-events';
@@ -1,4 +1,6 @@
1
+ import { catchError, defer, mergeMap, of } from 'rxjs';
1
2
  import { z } from 'zod';
3
+ import { toolErrorEvent, toolResultToEvents } from './tool-result-events';
2
4
  export function tool(definition) {
3
5
  return { ...definition };
4
6
  }
@@ -38,7 +40,7 @@ export function localTools(tools) {
38
40
  parameters: zodToJsonSchema(toolDef.schema),
39
41
  };
40
42
  },
41
- execute: async (toolCall, context) => {
43
+ execute: (toolCall, context) => defer(async () => {
42
44
  const toolDef = toolMap.get(toolCall.function.name);
43
45
  if (!toolDef) {
44
46
  return {
@@ -80,6 +82,6 @@ export function localTools(tools) {
80
82
  error: err.message,
81
83
  };
82
84
  }
83
- },
85
+ }).pipe(mergeMap((result) => toolResultToEvents(context, toolCall, result)), catchError((error) => of(toolErrorEvent(context, toolCall, error instanceof Error ? error.message : String(error))))),
84
86
  };
85
87
  }
@@ -1,5 +1,5 @@
1
1
  import type { AuthContext, ExecutionContext } from '../types/context';
2
- import type { ToolCall, ToolDefinition, ToolProvider, ToolResult } from '../types/tools';
2
+ import type { ToolCall, ToolDefinition, ToolProvider } from '../types/tools';
3
3
  export interface MCPProviderConfig {
4
4
  serverId: string;
5
5
  serverUrl: string;
@@ -17,8 +17,7 @@ export declare class McpToolProvider implements ToolProvider {
17
17
  private ongoingRequest;
18
18
  constructor(config: MCPProviderConfig);
19
19
  getTool(toolName: string): Promise<ToolDefinition | undefined>;
20
- executeBatch(toolCalls: ToolCall[], context: ExecutionContext): Promise<ToolResult[]>;
21
20
  getTools(): Promise<ToolDefinition[]>;
22
- execute(toolCall: ToolCall, context: ExecutionContext): Promise<ToolResult>;
21
+ execute(toolCall: ToolCall, context: ExecutionContext): import("rxjs").Observable<import("..").ContextAnyEvent | import("..").ContextEvent<import("..").ToolCompleteEvent>>;
23
22
  private convertMCPTool;
24
23
  }
@@ -1,4 +1,6 @@
1
+ import { catchError, defer, mergeMap, of } from 'rxjs';
1
2
  import { MCPClient } from './mcp-client';
3
+ import { toolErrorEvent, toolResultToEvents } from './tool-result-events';
2
4
  export const mcp = (config) => {
3
5
  return new McpToolProvider(config);
4
6
  };
@@ -22,9 +24,6 @@ export class McpToolProvider {
22
24
  const tools = await this.getTools();
23
25
  return tools.find((tool) => tool.name === toolName);
24
26
  }
25
- async executeBatch(toolCalls, context) {
26
- return Promise.all(toolCalls.map((call) => this.execute(call, context)));
27
- }
28
27
  async getTools() {
29
28
  if (this.toolCache.size > 0 && this.cacheExpiry && Date.now() < this.cacheExpiry) {
30
29
  return Array.from(this.toolCache.values());
@@ -48,38 +47,40 @@ export class McpToolProvider {
48
47
  });
49
48
  return this.ongoingRequest;
50
49
  }
51
- async execute(toolCall, context) {
52
- const { name, arguments: args } = toolCall.function;
53
- if (typeof args !== 'object' || args === null) {
54
- return {
55
- toolCallId: toolCall.id,
56
- toolName: name,
57
- success: false,
58
- error: 'Tool arguments must be an object',
59
- result: null,
60
- };
61
- }
62
- try {
63
- const response = await this.client.callTool({
64
- name,
65
- arguments: args,
66
- }, context.authContext);
67
- return {
68
- toolCallId: toolCall.id,
69
- toolName: name,
70
- success: true,
71
- result: response.result,
72
- };
73
- }
74
- catch (error) {
75
- return {
76
- toolCallId: toolCall.id,
77
- toolName: name,
78
- success: false,
79
- error: error instanceof Error ? error.message : String(error),
80
- result: null,
81
- };
82
- }
50
+ execute(toolCall, context) {
51
+ return defer(async () => {
52
+ const { name, arguments: args } = toolCall.function;
53
+ if (typeof args !== 'object' || args === null) {
54
+ return {
55
+ toolCallId: toolCall.id,
56
+ toolName: name,
57
+ success: false,
58
+ error: 'Tool arguments must be an object',
59
+ result: null,
60
+ };
61
+ }
62
+ try {
63
+ const response = await this.client.callTool({
64
+ name,
65
+ arguments: args,
66
+ }, context.authContext);
67
+ return {
68
+ toolCallId: toolCall.id,
69
+ toolName: name,
70
+ success: true,
71
+ result: response.result,
72
+ };
73
+ }
74
+ catch (error) {
75
+ return {
76
+ toolCallId: toolCall.id,
77
+ toolName: name,
78
+ success: false,
79
+ error: error instanceof Error ? error.message : String(error),
80
+ result: null,
81
+ };
82
+ }
83
+ }).pipe(mergeMap((result) => toolResultToEvents(context, toolCall, result)), catchError((error) => of(toolErrorEvent(context, toolCall, error instanceof Error ? error.message : String(error)))));
83
84
  }
84
85
  convertMCPTool = (mcpTool) => {
85
86
  return {
@@ -0,0 +1,6 @@
1
+ import { type Observable } from 'rxjs';
2
+ import type { ExecutionContext } from '../types/context';
3
+ import type { ContextAnyEvent, ContextEvent, ToolCompleteEvent } from '../types/event';
4
+ import type { ToolCall, ToolResult } from '../types/tools';
5
+ export declare const toolErrorEvent: (context: ExecutionContext, toolCall: ToolCall, errorMessage: string) => ContextEvent<ToolCompleteEvent>;
6
+ export declare const toolResultToEvents: (context: ExecutionContext, _toolCall: ToolCall, result: ToolResult) => Observable<ContextAnyEvent>;
@@ -0,0 +1,33 @@
1
+ import { concat, EMPTY, from, of } from 'rxjs';
2
+ export const toolErrorEvent = (context, toolCall, errorMessage) => ({
3
+ kind: 'tool-complete',
4
+ contextId: context.contextId,
5
+ taskId: context.taskId,
6
+ toolCallId: toolCall.id,
7
+ toolName: toolCall.function.name,
8
+ success: false,
9
+ result: null,
10
+ error: errorMessage,
11
+ timestamp: new Date().toISOString(),
12
+ });
13
+ export const toolResultToEvents = (context, _toolCall, result) => {
14
+ const toolCompleteEvent = {
15
+ kind: 'tool-complete',
16
+ contextId: context.contextId,
17
+ taskId: context.taskId,
18
+ toolCallId: result.toolCallId,
19
+ toolName: result.toolName,
20
+ success: result.success,
21
+ result: result.result,
22
+ error: result.error,
23
+ timestamp: new Date().toISOString(),
24
+ };
25
+ const messageEvents = result.messages?.map((message) => ({
26
+ kind: 'internal:tool-message',
27
+ contextId: context.contextId,
28
+ taskId: context.taskId,
29
+ message,
30
+ timestamp: new Date().toISOString(),
31
+ })) ?? [];
32
+ return concat(of(toolCompleteEvent), messageEvents.length > 0 ? from(messageEvents) : EMPTY);
33
+ };