@looopy-ai/core 1.1.1 → 1.1.3
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/dist/core/agent.d.ts +2 -1
- package/dist/core/agent.js +0 -1
- package/dist/core/iteration.js +12 -10
- package/dist/core/loop.d.ts +1 -2
- package/dist/core/loop.js +9 -45
- package/dist/core/tools.js +4 -2
- package/dist/observability/spans/llm-call.d.ts +2 -1
- package/dist/observability/spans/llm-call.js +3 -1
- package/dist/observability/tracing.d.ts +2 -0
- package/dist/observability/tracing.js +2 -0
- package/dist/providers/litellm-provider.js +2 -2
- package/dist/tools/artifact-tools.js +314 -244
- package/dist/tools/local-tools.d.ts +1 -1
- package/dist/tools/local-tools.js +2 -2
- package/dist/utils/prompt.d.ts +7 -0
- package/dist/utils/prompt.js +12 -0
- package/dist/utils/recursive-merge.d.ts +7 -0
- package/dist/utils/recursive-merge.js +29 -0
- package/package.json +1 -1
package/dist/core/agent.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { AnyEvent } from '../types/event';
|
|
|
6
6
|
import type { LLMProvider } from '../types/llm';
|
|
7
7
|
import type { Message } from '../types/message';
|
|
8
8
|
import type { ToolProvider } from '../types/tools';
|
|
9
|
+
import type { SystemPromptProp } from '../utils/prompt';
|
|
9
10
|
export interface AgentConfig {
|
|
10
11
|
agentId: string;
|
|
11
12
|
contextId: string;
|
|
@@ -15,7 +16,7 @@ export interface AgentConfig {
|
|
|
15
16
|
agentStore?: AgentStore;
|
|
16
17
|
autoCompact?: boolean;
|
|
17
18
|
maxMessages?: number;
|
|
18
|
-
systemPrompt?:
|
|
19
|
+
systemPrompt?: SystemPromptProp;
|
|
19
20
|
logger?: import('pino').Logger;
|
|
20
21
|
}
|
|
21
22
|
export interface GetMessagesOptions {
|
package/dist/core/agent.js
CHANGED
|
@@ -14,7 +14,6 @@ export class Agent {
|
|
|
14
14
|
this.config = {
|
|
15
15
|
autoCompact: false,
|
|
16
16
|
maxMessages: 100,
|
|
17
|
-
systemPrompt: 'You are a helpful AI assistant.',
|
|
18
17
|
...config,
|
|
19
18
|
logger: config.logger?.child({ contextId: config.contextId }) ||
|
|
20
19
|
getLogger({ contextId: config.contextId }),
|
package/dist/core/iteration.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { concat, defer, filter, map, mergeMap, shareReplay } from 'rxjs';
|
|
2
2
|
import { startLLMCallSpan, startLoopIterationSpan } from '../observability/spans';
|
|
3
|
+
import { getSystemPrompt } from '../utils/prompt';
|
|
3
4
|
import { runToolCall } from './tools';
|
|
4
5
|
export const runIteration = (context, config, history) => {
|
|
5
6
|
const logger = context.logger.child({
|
|
@@ -8,11 +9,12 @@ export const runIteration = (context, config, history) => {
|
|
|
8
9
|
});
|
|
9
10
|
const { traceContext: iterationContext, tapFinish: finishIterationSpan } = startLoopIterationSpan({ ...context, logger }, config.iterationNumber);
|
|
10
11
|
const llmEvents$ = defer(async () => {
|
|
11
|
-
const
|
|
12
|
+
const systemPrompt = await getSystemPrompt(context.systemPrompt);
|
|
13
|
+
const messages = await prepareMessages(systemPrompt, context.skillPrompts, history);
|
|
12
14
|
const tools = await prepareTools(context.toolProviders);
|
|
13
|
-
return { messages, tools };
|
|
14
|
-
}).pipe(mergeMap(({ messages, tools }) => {
|
|
15
|
-
const { tapFinish: finishLLMCallSpan } = startLLMCallSpan({ ...context, parentContext: iterationContext }, messages);
|
|
15
|
+
return { messages, tools, systemPrompt };
|
|
16
|
+
}).pipe(mergeMap(({ messages, tools, systemPrompt }) => {
|
|
17
|
+
const { tapFinish: finishLLMCallSpan } = startLLMCallSpan({ ...context, parentContext: iterationContext }, systemPrompt, messages);
|
|
16
18
|
return config.llmProvider
|
|
17
19
|
.call({
|
|
18
20
|
messages,
|
|
@@ -33,17 +35,17 @@ export const runIteration = (context, config, history) => {
|
|
|
33
35
|
}, event)));
|
|
34
36
|
return concat(llmEvents$.pipe(filter((event) => event.kind !== 'tool-call')), toolEvents$).pipe(finishIterationSpan);
|
|
35
37
|
};
|
|
36
|
-
const prepareMessages = (
|
|
38
|
+
const prepareMessages = async (systemPrompt, skillPrompts, history) => {
|
|
37
39
|
const messages = [];
|
|
38
|
-
if (
|
|
40
|
+
if (systemPrompt) {
|
|
39
41
|
messages.push({
|
|
40
42
|
role: 'system',
|
|
41
|
-
content:
|
|
42
|
-
name: 'system-prompt',
|
|
43
|
+
content: systemPrompt.prompt,
|
|
44
|
+
name: systemPrompt.name || 'system-prompt',
|
|
43
45
|
});
|
|
44
46
|
}
|
|
45
|
-
if (
|
|
46
|
-
for (const [name, content] of Object.entries(
|
|
47
|
+
if (skillPrompts) {
|
|
48
|
+
for (const [name, content] of Object.entries(skillPrompts)) {
|
|
47
49
|
messages.push({
|
|
48
50
|
role: 'system',
|
|
49
51
|
content,
|
package/dist/core/loop.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { type Observable } from 'rxjs';
|
|
2
1
|
import type { AnyEvent } from '../types/event';
|
|
3
2
|
import type { Message } from '../types/message';
|
|
4
3
|
import type { LoopConfig, TurnContext } from './types';
|
|
5
|
-
export declare const runLoop: (context: TurnContext, config: LoopConfig, history: Message[]) => Observable<AnyEvent>;
|
|
4
|
+
export declare const runLoop: (context: TurnContext, config: LoopConfig, history: Message[]) => import("rxjs").Observable<AnyEvent>;
|
package/dist/core/loop.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { concat, EMPTY,
|
|
1
|
+
import { concat, EMPTY, mergeMap, of, reduce, shareReplay } from 'rxjs';
|
|
2
2
|
import { createTaskCompleteEvent, createTaskCreatedEvent, createTaskStatusEvent } from '../events';
|
|
3
3
|
import { startAgentLoopSpan } from '../observability/spans';
|
|
4
|
+
import { recursiveMerge } from '../utils/recursive-merge';
|
|
4
5
|
import { runIteration } from './iteration';
|
|
5
6
|
export const runLoop = (context, config, history) => {
|
|
6
7
|
const logger = context.logger.child({ component: 'loop' });
|
|
@@ -49,34 +50,6 @@ export const runLoop = (context, config, history) => {
|
|
|
49
50
|
}));
|
|
50
51
|
return concat(of(taskEvent, workingEvent), merged$, finalSummary$).pipe(tapFinish);
|
|
51
52
|
};
|
|
52
|
-
function recursiveMerge(initial, eventsFor, next, isStop) {
|
|
53
|
-
const seed = {
|
|
54
|
-
state: initial,
|
|
55
|
-
iteration: 0,
|
|
56
|
-
events$: eventsFor({ ...initial, iteration: 0 }).pipe(shareReplay()),
|
|
57
|
-
};
|
|
58
|
-
const iterations$ = of(seed).pipe(expand(({ state, iteration, events$ }) => events$.pipe(reduce((acc, e) => {
|
|
59
|
-
acc.events.push(e);
|
|
60
|
-
if (isStop(e))
|
|
61
|
-
acc.sawStop = true;
|
|
62
|
-
return acc;
|
|
63
|
-
}, { events: [], sawStop: false }), mergeMap(({ events, sawStop }) => {
|
|
64
|
-
if (sawStop)
|
|
65
|
-
return EMPTY;
|
|
66
|
-
return of(next(state, { iteration, events })).pipe(map((nextState) => {
|
|
67
|
-
const nextIter = iteration + 1;
|
|
68
|
-
return {
|
|
69
|
-
state: nextState,
|
|
70
|
-
iteration: nextIter,
|
|
71
|
-
events$: eventsFor({
|
|
72
|
-
...nextState,
|
|
73
|
-
iteration: nextIter,
|
|
74
|
-
}).pipe(share()),
|
|
75
|
-
};
|
|
76
|
-
}));
|
|
77
|
-
}))));
|
|
78
|
-
return iterations$.pipe(mergeMap(({ events$ }) => events$));
|
|
79
|
-
}
|
|
80
53
|
const eventsToMessages = (events) => {
|
|
81
54
|
const messages = [];
|
|
82
55
|
for (const event of events) {
|
|
@@ -88,22 +61,13 @@ const eventsToMessages = (events) => {
|
|
|
88
61
|
content: event.content,
|
|
89
62
|
});
|
|
90
63
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
id: event.toolCallId,
|
|
99
|
-
type: 'function',
|
|
100
|
-
function: {
|
|
101
|
-
name: event.toolName,
|
|
102
|
-
arguments: event.arguments,
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
});
|
|
64
|
+
if (event.finishReason === 'tool_calls' && (event.toolCalls?.length ?? 0) > 0) {
|
|
65
|
+
messages.push({
|
|
66
|
+
role: 'assistant',
|
|
67
|
+
content: '',
|
|
68
|
+
toolCalls: event.toolCalls,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
107
71
|
break;
|
|
108
72
|
case 'tool-complete':
|
|
109
73
|
messages.push({
|
package/dist/core/tools.js
CHANGED
|
@@ -17,7 +17,7 @@ export const runToolCall = (context, toolCall) => {
|
|
|
17
17
|
return of(toolCall);
|
|
18
18
|
}
|
|
19
19
|
const { provider, tool } = matchingProvider;
|
|
20
|
-
logger.
|
|
20
|
+
logger.debug({ providerName: provider.name, toolIcon: tool.icon }, 'Found tool provider for tool');
|
|
21
21
|
const toolStartEvent = {
|
|
22
22
|
kind: 'tool-start',
|
|
23
23
|
contextId: context.contextId,
|
|
@@ -43,7 +43,9 @@ export const runToolCall = (context, toolCall) => {
|
|
|
43
43
|
logger.trace({
|
|
44
44
|
success: result.success,
|
|
45
45
|
}, 'Tool execution complete');
|
|
46
|
-
return
|
|
46
|
+
return result.success
|
|
47
|
+
? createToolCompleteEvent(context, toolCall, result.result)
|
|
48
|
+
: createToolErrorEvent(context, toolCall, result.error || 'Unknown error');
|
|
47
49
|
}
|
|
48
50
|
catch (error) {
|
|
49
51
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { LoopContext } from '../../core/types';
|
|
2
2
|
import type { AnyEvent } from '../../types/event';
|
|
3
3
|
import type { Message } from '../../types/message';
|
|
4
|
+
import type { SystemPrompt } from '../../utils/prompt';
|
|
4
5
|
export interface LLMCallSpanParams {
|
|
5
6
|
agentId: string;
|
|
6
7
|
taskId: string;
|
|
7
8
|
messages: Message[];
|
|
8
9
|
parentContext: import('@opentelemetry/api').Context;
|
|
9
10
|
}
|
|
10
|
-
export declare const startLLMCallSpan: (context: LoopContext, messages: Message[]) => {
|
|
11
|
+
export declare const startLLMCallSpan: (context: LoopContext, systemPrompt: SystemPrompt | undefined, messages: Message[]) => {
|
|
11
12
|
span: import("@opentelemetry/api").Span;
|
|
12
13
|
traceContext: import("@opentelemetry/api").Context;
|
|
13
14
|
tapFinish: import("rxjs").MonoTypeOperatorFunction<AnyEvent>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SpanStatusCode, trace } from '@opentelemetry/api';
|
|
2
2
|
import { tap } from 'rxjs/internal/operators/tap';
|
|
3
3
|
import { SpanAttributes, SpanNames } from '../tracing';
|
|
4
|
-
export const startLLMCallSpan = (context, messages) => {
|
|
4
|
+
export const startLLMCallSpan = (context, systemPrompt, messages) => {
|
|
5
5
|
const tracer = trace.getTracer('looopy');
|
|
6
6
|
const span = tracer.startSpan(SpanNames.LLM_CALL, {
|
|
7
7
|
attributes: {
|
|
@@ -9,6 +9,8 @@ export const startLLMCallSpan = (context, messages) => {
|
|
|
9
9
|
[SpanAttributes.TASK_ID]: context.taskId,
|
|
10
10
|
[SpanAttributes.GEN_AI_PROMPT]: JSON.stringify(messages),
|
|
11
11
|
[SpanAttributes.LANGFUSE_OBSERVATION_TYPE]: 'generation',
|
|
12
|
+
[SpanAttributes.LANGFUSE_PROMPT_NAME]: systemPrompt?.name,
|
|
13
|
+
[SpanAttributes.LANGFUSE_PROMPT_VERSION]: systemPrompt?.version,
|
|
12
14
|
},
|
|
13
15
|
}, context.parentContext);
|
|
14
16
|
const traceContext = trace.setSpan(context.parentContext, span);
|
|
@@ -33,6 +33,8 @@ export declare const SpanAttributes: {
|
|
|
33
33
|
readonly LANGFUSE_METADATA: "langfuse.metadata";
|
|
34
34
|
readonly LANGFUSE_VERSION: "langfuse.version";
|
|
35
35
|
readonly LANGFUSE_RELEASE: "langfuse.release";
|
|
36
|
+
readonly LANGFUSE_PROMPT_NAME: "langfuse.prompt.name";
|
|
37
|
+
readonly LANGFUSE_PROMPT_VERSION: "langfuse.prompt.version";
|
|
36
38
|
readonly GEN_AI_SYSTEM: "gen_ai.system";
|
|
37
39
|
readonly GEN_AI_REQUEST_MODEL: "gen_ai.request.model";
|
|
38
40
|
readonly GEN_AI_RESPONSE_MODEL: "gen_ai.response.model";
|
|
@@ -178,6 +178,8 @@ export const SpanAttributes = {
|
|
|
178
178
|
LANGFUSE_METADATA: 'langfuse.metadata',
|
|
179
179
|
LANGFUSE_VERSION: 'langfuse.version',
|
|
180
180
|
LANGFUSE_RELEASE: 'langfuse.release',
|
|
181
|
+
LANGFUSE_PROMPT_NAME: 'langfuse.prompt.name',
|
|
182
|
+
LANGFUSE_PROMPT_VERSION: 'langfuse.prompt.version',
|
|
181
183
|
GEN_AI_SYSTEM: 'gen_ai.system',
|
|
182
184
|
GEN_AI_REQUEST_MODEL: 'gen_ai.request.model',
|
|
183
185
|
GEN_AI_RESPONSE_MODEL: 'gen_ai.response.model',
|
|
@@ -107,14 +107,14 @@ export class LiteLLMProvider {
|
|
|
107
107
|
...usage,
|
|
108
108
|
timestamp: new Date().toISOString(),
|
|
109
109
|
})), this.debugLog('llm-usage'));
|
|
110
|
-
const toolCalls$ = contentComplete$.pipe(mergeMap((event) => event.toolCalls?.map((tc) => ({
|
|
110
|
+
const toolCalls$ = contentComplete$.pipe(filter((event) => event.finishReason === 'tool_calls'), mergeMap((event) => event.toolCalls?.map((tc) => ({
|
|
111
111
|
kind: 'tool-call',
|
|
112
112
|
toolCallId: tc.id,
|
|
113
113
|
toolName: tc.function.name,
|
|
114
114
|
arguments: tc.function.arguments,
|
|
115
115
|
timestamp: event.timestamp,
|
|
116
116
|
})) || []));
|
|
117
|
-
return merge(contentDeltas$, thoughts$, usageComplete
|
|
117
|
+
return merge(contentDeltas$, thoughts$, usageComplete$).pipe(concatWith(toolCalls$), concatWith(contentComplete$));
|
|
118
118
|
}
|
|
119
119
|
debugLogRawChunk(chunk) {
|
|
120
120
|
if (!this.config.debugLogPath) {
|
|
@@ -13,265 +13,335 @@ async function trackArtifactInState(taskId, artifactId, taskStateStore) {
|
|
|
13
13
|
export function createArtifactTools(artifactStore, taskStateStore) {
|
|
14
14
|
const scheduledStore = new ArtifactScheduler(artifactStore);
|
|
15
15
|
return localTools([
|
|
16
|
-
tool(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
.string()
|
|
24
|
-
.optional()
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
: '
|
|
56
|
-
|
|
16
|
+
tool({
|
|
17
|
+
name: 'create_file_artifact',
|
|
18
|
+
description: 'Create a new file artifact for streaming text or binary content. Use append_file_chunk to add content. Set override=true to replace existing artifact.',
|
|
19
|
+
schema: z.object({
|
|
20
|
+
artifactId: z
|
|
21
|
+
.string()
|
|
22
|
+
.describe('Unique identifier for the artifact (e.g., "report-2025", "analysis-results")'),
|
|
23
|
+
name: z.string().optional().describe('Human-readable name for the artifact'),
|
|
24
|
+
description: z.string().optional().describe('Description of the artifact content'),
|
|
25
|
+
mimeType: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.default('text/plain')
|
|
29
|
+
.describe('MIME type of the content (e.g., "text/plain", "text/markdown")'),
|
|
30
|
+
encoding: z
|
|
31
|
+
.enum(['utf-8', 'base64'])
|
|
32
|
+
.optional()
|
|
33
|
+
.default('utf-8')
|
|
34
|
+
.describe('Content encoding'),
|
|
35
|
+
override: z
|
|
36
|
+
.boolean()
|
|
37
|
+
.optional()
|
|
38
|
+
.default(false)
|
|
39
|
+
.describe('Set to true to replace an existing artifact with the same ID'),
|
|
40
|
+
}),
|
|
41
|
+
handler: async (params, context) => {
|
|
42
|
+
await scheduledStore.createFileArtifact({
|
|
43
|
+
artifactId: params.artifactId,
|
|
44
|
+
taskId: context.taskId,
|
|
45
|
+
contextId: context.contextId,
|
|
46
|
+
name: params.name,
|
|
47
|
+
description: params.description,
|
|
48
|
+
mimeType: params.mimeType,
|
|
49
|
+
encoding: params.encoding,
|
|
50
|
+
override: params.override,
|
|
51
|
+
});
|
|
52
|
+
await trackArtifactInState(context.taskId, params.artifactId, taskStateStore);
|
|
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.',
|
|
60
|
+
};
|
|
61
|
+
},
|
|
57
62
|
}),
|
|
58
|
-
tool(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.
|
|
63
|
-
.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
:
|
|
77
|
-
|
|
63
|
+
tool({
|
|
64
|
+
name: 'append_file_chunk',
|
|
65
|
+
description: 'Append a chunk of content to a file artifact. Call multiple times to stream content.',
|
|
66
|
+
schema: z.object({
|
|
67
|
+
artifactId: z.string().describe('The artifact ID to append to'),
|
|
68
|
+
content_chunk: z.string().describe('Content chunk to append to the file'),
|
|
69
|
+
isLastChunk: z
|
|
70
|
+
.boolean()
|
|
71
|
+
.optional()
|
|
72
|
+
.default(false)
|
|
73
|
+
.describe('Set to true on the final chunk to mark artifact as complete'),
|
|
74
|
+
}),
|
|
75
|
+
handler: async (params, context) => {
|
|
76
|
+
await scheduledStore.appendFileChunk(context.contextId, params.artifactId, params.content_chunk, {
|
|
77
|
+
isLastChunk: params.isLastChunk,
|
|
78
|
+
});
|
|
79
|
+
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.',
|
|
86
|
+
};
|
|
87
|
+
},
|
|
78
88
|
}),
|
|
79
|
-
tool(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
tool({
|
|
90
|
+
name: 'get_file_content',
|
|
91
|
+
description: 'Get the complete content of a file artifact',
|
|
92
|
+
schema: z.object({
|
|
93
|
+
artifactId: z.string().describe('The artifact ID to retrieve'),
|
|
94
|
+
}),
|
|
95
|
+
handler: async (params, context) => {
|
|
96
|
+
const content = await scheduledStore.getFileContent(context.contextId, params.artifactId);
|
|
97
|
+
return {
|
|
98
|
+
artifactId: params.artifactId,
|
|
99
|
+
content,
|
|
100
|
+
};
|
|
101
|
+
},
|
|
87
102
|
}),
|
|
88
|
-
tool(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
.
|
|
95
|
-
.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
: '
|
|
116
|
-
|
|
103
|
+
tool({
|
|
104
|
+
name: 'create_data_artifact',
|
|
105
|
+
description: 'Create a data artifact with structured JSON data. Set override=true to replace existing artifact.',
|
|
106
|
+
schema: z.object({
|
|
107
|
+
artifactId: z.string().describe('Unique identifier for the artifact'),
|
|
108
|
+
name: z.string().optional().describe('Human-readable name'),
|
|
109
|
+
description: z.string().optional().describe('Description of the data'),
|
|
110
|
+
data: z.record(z.string(), z.unknown()).describe('The structured data object'),
|
|
111
|
+
override: z
|
|
112
|
+
.boolean()
|
|
113
|
+
.optional()
|
|
114
|
+
.default(false)
|
|
115
|
+
.describe('Set to true to replace an existing artifact with the same ID'),
|
|
116
|
+
}),
|
|
117
|
+
handler: async (params, context) => {
|
|
118
|
+
await scheduledStore.createDataArtifact({
|
|
119
|
+
artifactId: params.artifactId,
|
|
120
|
+
taskId: context.taskId,
|
|
121
|
+
contextId: context.contextId,
|
|
122
|
+
name: params.name,
|
|
123
|
+
description: params.description,
|
|
124
|
+
override: params.override,
|
|
125
|
+
});
|
|
126
|
+
await scheduledStore.writeData(context.contextId, params.artifactId, params.data);
|
|
127
|
+
await trackArtifactInState(context.taskId, params.artifactId, taskStateStore);
|
|
128
|
+
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.',
|
|
135
|
+
};
|
|
136
|
+
},
|
|
117
137
|
}),
|
|
118
|
-
tool(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
138
|
+
tool({
|
|
139
|
+
name: 'update_data_artifact',
|
|
140
|
+
description: 'Update the data content of an existing data artifact',
|
|
141
|
+
schema: z.object({
|
|
142
|
+
artifactId: z.string().describe('The artifact ID to update'),
|
|
143
|
+
data: z.record(z.string(), z.unknown()).describe('The new data object'),
|
|
144
|
+
}),
|
|
145
|
+
handler: async (params, context) => {
|
|
146
|
+
await scheduledStore.writeData(context.contextId, params.artifactId, params.data);
|
|
147
|
+
return {
|
|
148
|
+
artifactId: params.artifactId,
|
|
149
|
+
type: 'data',
|
|
150
|
+
status: 'complete',
|
|
151
|
+
message: 'Data artifact updated successfully.',
|
|
152
|
+
};
|
|
153
|
+
},
|
|
129
154
|
}),
|
|
130
|
-
tool(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
155
|
+
tool({
|
|
156
|
+
name: 'get_data_content',
|
|
157
|
+
description: 'Get the content of a data artifact',
|
|
158
|
+
schema: z.object({
|
|
159
|
+
artifactId: z.string().describe('The artifact ID to retrieve'),
|
|
160
|
+
}),
|
|
161
|
+
handler: async (params, context) => {
|
|
162
|
+
const data = await scheduledStore.getDataContent(context.contextId, params.artifactId);
|
|
163
|
+
return {
|
|
164
|
+
artifactId: params.artifactId,
|
|
165
|
+
data,
|
|
166
|
+
};
|
|
167
|
+
},
|
|
138
168
|
}),
|
|
139
|
-
tool(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
169
|
+
tool({
|
|
170
|
+
name: 'get_data_artifact',
|
|
171
|
+
description: 'Get the data content of a data artifact',
|
|
172
|
+
schema: z.object({
|
|
173
|
+
artifactId: z.string().describe('The artifact ID to retrieve'),
|
|
174
|
+
}),
|
|
175
|
+
handler: async (params, context) => {
|
|
176
|
+
const data = await scheduledStore.getDataContent(context.contextId, params.artifactId);
|
|
177
|
+
return {
|
|
178
|
+
artifactId: params.artifactId,
|
|
179
|
+
data,
|
|
180
|
+
};
|
|
181
|
+
},
|
|
147
182
|
}),
|
|
148
|
-
tool(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
description: z.string().optional().describe('Description of the dataset'),
|
|
183
|
+
tool({
|
|
184
|
+
name: 'create_dataset_artifact',
|
|
185
|
+
description: 'Create a dataset artifact for tabular data with a schema. Set override=true to replace existing artifact.',
|
|
152
186
|
schema: z.object({
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
187
|
+
artifactId: z.string().describe('Unique identifier for the dataset'),
|
|
188
|
+
name: z.string().optional().describe('Human-readable name'),
|
|
189
|
+
description: z.string().optional().describe('Description of the dataset'),
|
|
190
|
+
schema: z.object({
|
|
191
|
+
columns: z.array(z.object({
|
|
192
|
+
name: z.string(),
|
|
193
|
+
type: z.enum(['string', 'number', 'boolean', 'date', 'json']),
|
|
194
|
+
description: z.string().optional(),
|
|
195
|
+
})),
|
|
196
|
+
}),
|
|
197
|
+
override: z
|
|
198
|
+
.boolean()
|
|
199
|
+
.optional()
|
|
200
|
+
.default(false)
|
|
201
|
+
.describe('Set to true to replace an existing artifact with the same ID'),
|
|
158
202
|
}),
|
|
159
|
-
|
|
160
|
-
.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
message: params.override
|
|
180
|
-
? 'Dataset artifact reset. Use append_dataset_row(s) to add data.'
|
|
181
|
-
: 'Dataset artifact created. Use append_dataset_row(s) to add data.',
|
|
182
|
-
};
|
|
203
|
+
handler: async (params, context) => {
|
|
204
|
+
await scheduledStore.createDatasetArtifact({
|
|
205
|
+
artifactId: params.artifactId,
|
|
206
|
+
taskId: context.taskId,
|
|
207
|
+
contextId: context.contextId,
|
|
208
|
+
name: params.name,
|
|
209
|
+
description: params.description,
|
|
210
|
+
schema: params.schema,
|
|
211
|
+
override: params.override,
|
|
212
|
+
});
|
|
213
|
+
await trackArtifactInState(context.taskId, params.artifactId, taskStateStore);
|
|
214
|
+
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.',
|
|
221
|
+
};
|
|
222
|
+
},
|
|
183
223
|
}),
|
|
184
|
-
tool(
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
224
|
+
tool({
|
|
225
|
+
name: 'append_dataset_row',
|
|
226
|
+
description: 'Append a single row to a dataset artifact',
|
|
227
|
+
schema: z.object({
|
|
228
|
+
artifactId: z.string().describe('The dataset artifact ID'),
|
|
229
|
+
row: z.record(z.string(), z.unknown()).describe('Row data matching the dataset schema'),
|
|
230
|
+
}),
|
|
231
|
+
handler: async (params, context) => {
|
|
232
|
+
await scheduledStore.appendDatasetBatch(context.contextId, params.artifactId, [params.row]);
|
|
233
|
+
return {
|
|
234
|
+
artifactId: params.artifactId,
|
|
235
|
+
rowAdded: true,
|
|
236
|
+
message: 'Row appended to dataset.',
|
|
237
|
+
};
|
|
238
|
+
},
|
|
194
239
|
}),
|
|
195
|
-
tool(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
isLastBatch:
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
240
|
+
tool({
|
|
241
|
+
name: 'append_dataset_rows',
|
|
242
|
+
description: 'Append multiple rows to a dataset artifact',
|
|
243
|
+
schema: z.object({
|
|
244
|
+
artifactId: z.string().describe('The dataset artifact ID'),
|
|
245
|
+
rows: z.array(z.record(z.string(), z.unknown())).describe('Array of rows to append'),
|
|
246
|
+
isLastBatch: z.boolean().optional().describe('Set to true on the final batch'),
|
|
247
|
+
}),
|
|
248
|
+
handler: async (params, context) => {
|
|
249
|
+
await scheduledStore.appendDatasetBatch(context.contextId, params.artifactId, params.rows, {
|
|
250
|
+
isLastBatch: params.isLastBatch,
|
|
251
|
+
});
|
|
252
|
+
return {
|
|
253
|
+
artifactId: params.artifactId,
|
|
254
|
+
rowsAdded: params.rows.length,
|
|
255
|
+
message: `${params.rows.length} rows appended to dataset.`,
|
|
256
|
+
};
|
|
257
|
+
},
|
|
208
258
|
}),
|
|
209
|
-
tool(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
259
|
+
tool({
|
|
260
|
+
name: 'get_dataset_rows',
|
|
261
|
+
description: 'Get all rows from a dataset artifact',
|
|
262
|
+
schema: z.object({
|
|
263
|
+
artifactId: z.string().describe('The dataset artifact ID'),
|
|
264
|
+
}),
|
|
265
|
+
handler: async (params, context) => {
|
|
266
|
+
const rows = await scheduledStore.getDatasetRows(context.contextId, params.artifactId);
|
|
267
|
+
return {
|
|
268
|
+
artifactId: params.artifactId,
|
|
269
|
+
rows,
|
|
270
|
+
totalRows: rows.length,
|
|
271
|
+
};
|
|
272
|
+
},
|
|
218
273
|
}),
|
|
219
|
-
tool(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
274
|
+
tool({
|
|
275
|
+
name: 'list_artifacts',
|
|
276
|
+
description: 'List all artifacts in the current context, optionally filtered by task',
|
|
277
|
+
schema: z.object({
|
|
278
|
+
taskId: z.string().optional().describe('Filter artifacts by task ID'),
|
|
279
|
+
}),
|
|
280
|
+
handler: async (params, context) => {
|
|
281
|
+
const artifactIds = await scheduledStore.listArtifacts(context.contextId, params.taskId);
|
|
282
|
+
const artifacts = await Promise.all(artifactIds.map((id) => scheduledStore.getArtifact(context.contextId, id)));
|
|
283
|
+
const validArtifacts = artifacts.filter((a) => a !== null);
|
|
284
|
+
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,
|
|
294
|
+
};
|
|
295
|
+
},
|
|
236
296
|
}),
|
|
237
|
-
tool(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
297
|
+
tool({
|
|
298
|
+
name: 'get_artifact',
|
|
299
|
+
description: 'Get metadata for a specific artifact by ID',
|
|
300
|
+
schema: z.object({
|
|
301
|
+
artifactId: z.string().describe('The artifact ID to retrieve'),
|
|
302
|
+
}),
|
|
303
|
+
handler: async (params, context) => {
|
|
304
|
+
const artifact = await scheduledStore.getArtifact(context.contextId, params.artifactId);
|
|
305
|
+
if (!artifact) {
|
|
306
|
+
throw new Error(`Artifact not found: ${params.artifactId}`);
|
|
307
|
+
}
|
|
308
|
+
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
|
+
}),
|
|
328
|
+
};
|
|
329
|
+
},
|
|
265
330
|
}),
|
|
266
|
-
tool(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
331
|
+
tool({
|
|
332
|
+
name: 'delete_artifact',
|
|
333
|
+
description: 'Delete an artifact by ID',
|
|
334
|
+
schema: z.object({
|
|
335
|
+
artifactId: z.string().describe('The artifact ID to delete'),
|
|
336
|
+
}),
|
|
337
|
+
handler: async (params, context) => {
|
|
338
|
+
await scheduledStore.deleteArtifact(context.contextId, params.artifactId);
|
|
339
|
+
return {
|
|
340
|
+
artifactId: params.artifactId,
|
|
341
|
+
deleted: true,
|
|
342
|
+
message: 'Artifact deleted successfully.',
|
|
343
|
+
};
|
|
344
|
+
},
|
|
275
345
|
}),
|
|
276
346
|
]);
|
|
277
347
|
}
|
|
@@ -9,5 +9,5 @@ export interface LocalToolDefinition<TSchema extends z.ZodObject> {
|
|
|
9
9
|
schema: TSchema;
|
|
10
10
|
handler: ToolHandler<z.infer<TSchema>>;
|
|
11
11
|
}
|
|
12
|
-
export declare function tool<TSchema extends z.ZodObject>(
|
|
12
|
+
export declare function tool<TSchema extends z.ZodObject>(definition: LocalToolDefinition<TSchema>): LocalToolDefinition<TSchema>;
|
|
13
13
|
export declare function localTools(tools: LocalToolDefinition<z.ZodObject>[]): ToolProvider;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export function tool(
|
|
3
|
-
return {
|
|
2
|
+
export function tool(definition) {
|
|
3
|
+
return { ...definition };
|
|
4
4
|
}
|
|
5
5
|
const zodToJsonSchema = (schema) => {
|
|
6
6
|
const fullSchema = z.toJSONSchema(schema);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type SystemPrompt = {
|
|
2
|
+
prompt: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
version?: string;
|
|
5
|
+
};
|
|
6
|
+
export type SystemPromptProp = string | SystemPrompt | (() => Promise<SystemPrompt> | SystemPrompt);
|
|
7
|
+
export declare const getSystemPrompt: (systemPrompt?: SystemPromptProp) => Promise<SystemPrompt | undefined>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const getSystemPrompt = async (systemPrompt) => {
|
|
2
|
+
if (!systemPrompt) {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
5
|
+
if (typeof systemPrompt === 'string') {
|
|
6
|
+
return { prompt: systemPrompt };
|
|
7
|
+
}
|
|
8
|
+
if (typeof systemPrompt === 'function') {
|
|
9
|
+
return await systemPrompt();
|
|
10
|
+
}
|
|
11
|
+
return systemPrompt;
|
|
12
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Observable } from 'rxjs';
|
|
2
|
+
export declare function recursiveMerge<S, E>(initial: S, eventsFor: (state: S & {
|
|
3
|
+
iteration: number;
|
|
4
|
+
}) => Observable<E>, next: (state: S, info: {
|
|
5
|
+
iteration: number;
|
|
6
|
+
events: E[];
|
|
7
|
+
}) => S, isStop: (e: E) => boolean): Observable<E>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { EMPTY, expand, map, mergeMap, of, reduce, share, shareReplay, } from 'rxjs';
|
|
2
|
+
export function recursiveMerge(initial, eventsFor, next, isStop) {
|
|
3
|
+
const seed = {
|
|
4
|
+
state: initial,
|
|
5
|
+
iteration: 0,
|
|
6
|
+
events$: eventsFor({ ...initial, iteration: 0 }).pipe(shareReplay()),
|
|
7
|
+
};
|
|
8
|
+
const iterations$ = of(seed).pipe(expand(({ state, iteration, events$ }) => events$.pipe(reduce((acc, e) => {
|
|
9
|
+
acc.events.push(e);
|
|
10
|
+
if (isStop(e))
|
|
11
|
+
acc.sawStop = true;
|
|
12
|
+
return acc;
|
|
13
|
+
}, { events: [], sawStop: false }), mergeMap(({ events, sawStop }) => {
|
|
14
|
+
if (sawStop)
|
|
15
|
+
return EMPTY;
|
|
16
|
+
return of(next(state, { iteration, events })).pipe(map((nextState) => {
|
|
17
|
+
const nextIter = iteration + 1;
|
|
18
|
+
return {
|
|
19
|
+
state: nextState,
|
|
20
|
+
iteration: nextIter,
|
|
21
|
+
events$: eventsFor({
|
|
22
|
+
...nextState,
|
|
23
|
+
iteration: nextIter,
|
|
24
|
+
}).pipe(share()),
|
|
25
|
+
};
|
|
26
|
+
}));
|
|
27
|
+
}))));
|
|
28
|
+
return iterations$.pipe(mergeMap(({ events$ }) => events$));
|
|
29
|
+
}
|