@agent-creator/core 0.4.2 → 0.5.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.
- package/README.md +97 -0
- package/dist/defaults.d.ts +56 -6
- package/dist/defaults.js +225 -20
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/openAICompatibleProvider.d.ts +5 -1
- package/dist/openAICompatibleProvider.js +69 -10
- package/dist/runtime.d.ts +3 -0
- package/dist/runtime.js +49 -8
- package/dist/skills/webhook.d.ts +85 -0
- package/dist/skills/webhook.js +132 -0
- package/dist/types.d.ts +31 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,3 +22,100 @@ await agent.run({ input: 'Run my task', sessionId: 'session-1' });
|
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
`baseUrl`, `apiKey`, and `model` are required. The package does not read environment variables automatically.
|
|
25
|
+
|
|
26
|
+
## Built-In Modules
|
|
27
|
+
|
|
28
|
+
`@agent-creator/core` includes lightweight defaults that can be replaced one at a time:
|
|
29
|
+
|
|
30
|
+
- `InMemoryProvider`: process-local session memory with optional message/session limits and TTL.
|
|
31
|
+
- `BasicGuard` / `DefaultGuard`: configurable max input length, blocklist, and allowlist checks.
|
|
32
|
+
- `DefaultPlanner`: explicit skill routing via `metadata.skill`, single-skill auto routing, and `skill.name:` prefix routing.
|
|
33
|
+
- `ModelSkillPlanner`: optional model-driven skill selection that falls back to normal model responses.
|
|
34
|
+
- `DefaultExecutor`: validates skill I/O, applies optional skill `timeoutMs` and `retry`, and emits progress events.
|
|
35
|
+
- `ConsoleTraceProvider` and `InMemoryTraceProvider`: built-in tracing for development and tests.
|
|
36
|
+
- `HttpWebhookService` / `NoopWebhookService`: optional webhook notifications available from planners and skills.
|
|
37
|
+
|
|
38
|
+
Skills can declare optional execution metadata:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const skill = {
|
|
42
|
+
name: 'calendar.search',
|
|
43
|
+
description: 'Search calendar events',
|
|
44
|
+
inputSchema,
|
|
45
|
+
outputSchema,
|
|
46
|
+
permission: 'user_private',
|
|
47
|
+
timeoutMs: 5000,
|
|
48
|
+
retry: 1,
|
|
49
|
+
tags: ['calendar'],
|
|
50
|
+
async execute(input, context) {
|
|
51
|
+
return searchCalendar(input, context.userId);
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`metadata.skill` and `metadata.skillInput` are stable default-planner conventions for directly invoking a skill:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
await agent.run({
|
|
60
|
+
input: 'Search calendar',
|
|
61
|
+
metadata: {
|
|
62
|
+
skill: 'calendar.search',
|
|
63
|
+
skillInput: { query: 'today' },
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
OpenAI-compatible models also support optional generation parameters:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
createAgent({
|
|
72
|
+
model: {
|
|
73
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
74
|
+
apiKey: process.env.OPENAI_API_KEY!,
|
|
75
|
+
model: 'gpt-4o-mini',
|
|
76
|
+
systemPrompt: 'You are a concise assistant.',
|
|
77
|
+
temperature: 0.2,
|
|
78
|
+
maxTokens: 512,
|
|
79
|
+
responseFormat: 'json_object',
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Webhook Notifications
|
|
85
|
+
|
|
86
|
+
Webhook is a runtime service for developer-controlled side effects. Configure the URL in code or environment, then call it from a planner or skill through context:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const agent = createAgent({
|
|
90
|
+
model,
|
|
91
|
+
webhook: {
|
|
92
|
+
url: process.env.WEBHOOK_URL ?? '',
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
.useSkill({
|
|
96
|
+
name: 'build.run',
|
|
97
|
+
description: 'Run a build',
|
|
98
|
+
inputSchema,
|
|
99
|
+
outputSchema,
|
|
100
|
+
async execute(input, context) {
|
|
101
|
+
await context.webhook?.notify({
|
|
102
|
+
event: 'build.completed',
|
|
103
|
+
message: `Build completed for ${input.project}`,
|
|
104
|
+
});
|
|
105
|
+
return { ok: true };
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
.build();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Webhook delivery is best-effort by default: missing URLs, HTTP failures, and network errors do not fail the Agent run. The webhook URL is developer configuration and should not come from model output or user input.
|
|
112
|
+
|
|
113
|
+
For explicit Agent-triggered notifications, register the optional webhook skill:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { createWebhookSkill } from '@agent-creator/core';
|
|
117
|
+
|
|
118
|
+
builder.useSkill(createWebhookSkill({
|
|
119
|
+
url: process.env.WEBHOOK_URL ?? '',
|
|
120
|
+
}));
|
|
121
|
+
```
|
package/dist/defaults.d.ts
CHANGED
|
@@ -1,17 +1,45 @@
|
|
|
1
|
-
import type { AgentContext, AgentOutput, AgentPlan, Executor, ExecutorContext, Guard, MemoryMessage, MemoryProvider, Planner, TraceProvider, TraceRun } from './types.js';
|
|
1
|
+
import type { AgentContext, AgentOutput, AgentPlan, Executor, ExecutorContext, Guard, GuardResult, MemoryMessage, MemoryProvider, ModelProvider, Planner, TraceProvider, TraceRun, TraceEvent } from './types.js';
|
|
2
|
+
export interface InMemoryProviderOptions {
|
|
3
|
+
maxMessagesPerSession?: number;
|
|
4
|
+
maxSessions?: number;
|
|
5
|
+
ttlMs?: number;
|
|
6
|
+
now?: () => number;
|
|
7
|
+
}
|
|
2
8
|
export declare class InMemoryProvider implements MemoryProvider {
|
|
9
|
+
private readonly options;
|
|
3
10
|
private readonly sessions;
|
|
11
|
+
constructor(options?: InMemoryProviderOptions);
|
|
4
12
|
append(sessionId: string, message: MemoryMessage): void;
|
|
5
13
|
get(sessionId: string): MemoryMessage[];
|
|
6
14
|
clear(sessionId?: string): void;
|
|
15
|
+
pruneExpired(): void;
|
|
16
|
+
size(): number;
|
|
17
|
+
private enforceMaxSessions;
|
|
18
|
+
private now;
|
|
19
|
+
}
|
|
20
|
+
export interface BasicGuardOptions {
|
|
21
|
+
maxInputLength?: number;
|
|
22
|
+
blocklist?: Array<string | RegExp>;
|
|
23
|
+
allowlist?: Array<string | RegExp>;
|
|
24
|
+
}
|
|
25
|
+
export declare class BasicGuard implements Guard {
|
|
26
|
+
private readonly options;
|
|
27
|
+
constructor(options?: BasicGuardOptions);
|
|
28
|
+
check(context: AgentContext): GuardResult;
|
|
29
|
+
}
|
|
30
|
+
export declare class DefaultGuard extends BasicGuard {
|
|
7
31
|
}
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
32
|
+
export interface DefaultPlannerOptions {
|
|
33
|
+
model?: ModelProvider;
|
|
34
|
+
modelDrivenSkillSelection?: boolean;
|
|
12
35
|
}
|
|
13
36
|
export declare class DefaultPlanner implements Planner {
|
|
14
|
-
|
|
37
|
+
private readonly options;
|
|
38
|
+
constructor(options?: DefaultPlannerOptions);
|
|
39
|
+
plan(context: AgentContext): Promise<AgentPlan>;
|
|
40
|
+
}
|
|
41
|
+
export declare class ModelSkillPlanner extends DefaultPlanner {
|
|
42
|
+
constructor(model: ModelProvider);
|
|
15
43
|
}
|
|
16
44
|
export declare class DefaultExecutor implements Executor {
|
|
17
45
|
execute(plan: AgentPlan, context: ExecutorContext): Promise<AgentOutput>;
|
|
@@ -19,3 +47,25 @@ export declare class DefaultExecutor implements Executor {
|
|
|
19
47
|
export declare class NoopTraceProvider implements TraceProvider {
|
|
20
48
|
start(): TraceRun;
|
|
21
49
|
}
|
|
50
|
+
export declare class ConsoleTraceProvider implements TraceProvider {
|
|
51
|
+
private readonly logger;
|
|
52
|
+
constructor(logger?: Pick<Console, 'log' | 'error'>);
|
|
53
|
+
start(input: {
|
|
54
|
+
input: string;
|
|
55
|
+
}, traceId: string): TraceRun;
|
|
56
|
+
}
|
|
57
|
+
export interface StoredTraceRun {
|
|
58
|
+
traceId: string;
|
|
59
|
+
input: unknown;
|
|
60
|
+
events: TraceEvent[];
|
|
61
|
+
output?: AgentOutput;
|
|
62
|
+
startedAt: string;
|
|
63
|
+
endedAt?: string;
|
|
64
|
+
}
|
|
65
|
+
export declare class InMemoryTraceProvider implements TraceProvider {
|
|
66
|
+
private readonly runs;
|
|
67
|
+
start(input: unknown, traceId: string): TraceRun;
|
|
68
|
+
get(traceId: string): StoredTraceRun | undefined;
|
|
69
|
+
list(): StoredTraceRun[];
|
|
70
|
+
clear(traceId?: string): void;
|
|
71
|
+
}
|
package/dist/defaults.js
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
export class InMemoryProvider {
|
|
2
|
+
options;
|
|
2
3
|
sessions = new Map();
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.options = options;
|
|
6
|
+
}
|
|
3
7
|
append(sessionId, message) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
this.pruneExpired();
|
|
9
|
+
const current = this.sessions.get(sessionId)?.messages ?? [];
|
|
10
|
+
const messages = [...current, message];
|
|
11
|
+
const maxMessages = this.options.maxMessagesPerSession;
|
|
12
|
+
const trimmed = maxMessages && maxMessages > 0 ? messages.slice(-maxMessages) : messages;
|
|
13
|
+
this.sessions.delete(sessionId);
|
|
14
|
+
this.sessions.set(sessionId, { messages: trimmed, touchedAt: this.now() });
|
|
15
|
+
this.enforceMaxSessions();
|
|
7
16
|
}
|
|
8
17
|
get(sessionId) {
|
|
9
|
-
|
|
18
|
+
this.pruneExpired();
|
|
19
|
+
const session = this.sessions.get(sessionId);
|
|
20
|
+
if (!session)
|
|
21
|
+
return [];
|
|
22
|
+
session.touchedAt = this.now();
|
|
23
|
+
return [...session.messages];
|
|
10
24
|
}
|
|
11
25
|
clear(sessionId) {
|
|
12
26
|
if (sessionId)
|
|
@@ -14,14 +28,62 @@ export class InMemoryProvider {
|
|
|
14
28
|
else
|
|
15
29
|
this.sessions.clear();
|
|
16
30
|
}
|
|
31
|
+
pruneExpired() {
|
|
32
|
+
const ttlMs = this.options.ttlMs;
|
|
33
|
+
if (!ttlMs || ttlMs <= 0)
|
|
34
|
+
return;
|
|
35
|
+
const expiresBefore = this.now() - ttlMs;
|
|
36
|
+
for (const [sessionId, session] of this.sessions) {
|
|
37
|
+
if (session.touchedAt < expiresBefore)
|
|
38
|
+
this.sessions.delete(sessionId);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
size() {
|
|
42
|
+
this.pruneExpired();
|
|
43
|
+
return this.sessions.size;
|
|
44
|
+
}
|
|
45
|
+
enforceMaxSessions() {
|
|
46
|
+
const maxSessions = this.options.maxSessions;
|
|
47
|
+
if (!maxSessions || maxSessions <= 0)
|
|
48
|
+
return;
|
|
49
|
+
while (this.sessions.size > maxSessions) {
|
|
50
|
+
const oldest = this.sessions.keys().next().value;
|
|
51
|
+
if (!oldest)
|
|
52
|
+
break;
|
|
53
|
+
this.sessions.delete(oldest);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
now() {
|
|
57
|
+
return this.options.now?.() ?? Date.now();
|
|
58
|
+
}
|
|
17
59
|
}
|
|
18
|
-
export class
|
|
19
|
-
|
|
60
|
+
export class BasicGuard {
|
|
61
|
+
options;
|
|
62
|
+
constructor(options = {}) {
|
|
63
|
+
this.options = options;
|
|
64
|
+
}
|
|
65
|
+
check(context) {
|
|
66
|
+
const input = context.input.input;
|
|
67
|
+
if (this.options.maxInputLength && input.length > this.options.maxInputLength) {
|
|
68
|
+
return { allowed: false, reason: `Input exceeds max length of ${this.options.maxInputLength}.` };
|
|
69
|
+
}
|
|
70
|
+
if (this.options.allowlist?.length && !matchesAny(input, this.options.allowlist)) {
|
|
71
|
+
return { allowed: false, reason: 'Input is not allowed by the configured allowlist.' };
|
|
72
|
+
}
|
|
73
|
+
if (this.options.blocklist?.length && matchesAny(input, this.options.blocklist)) {
|
|
74
|
+
return { allowed: false, reason: 'Input was blocked by the configured blocklist.' };
|
|
75
|
+
}
|
|
20
76
|
return { allowed: true };
|
|
21
77
|
}
|
|
22
78
|
}
|
|
79
|
+
export class DefaultGuard extends BasicGuard {
|
|
80
|
+
}
|
|
23
81
|
export class DefaultPlanner {
|
|
24
|
-
|
|
82
|
+
options;
|
|
83
|
+
constructor(options = {}) {
|
|
84
|
+
this.options = options;
|
|
85
|
+
}
|
|
86
|
+
async plan(context) {
|
|
25
87
|
const requestedSkill = findRequestedSkill(context);
|
|
26
88
|
if (requestedSkill) {
|
|
27
89
|
return {
|
|
@@ -29,12 +91,26 @@ export class DefaultPlanner {
|
|
|
29
91
|
steps: [{ type: 'skill', skill: requestedSkill, input: context.input.metadata?.skillInput ?? context.input.input }],
|
|
30
92
|
};
|
|
31
93
|
}
|
|
94
|
+
if (this.options.modelDrivenSkillSelection && this.options.model && context.availableSkills.length > 0) {
|
|
95
|
+
const selected = await selectSkillWithModel(this.options.model, context);
|
|
96
|
+
if (selected) {
|
|
97
|
+
return {
|
|
98
|
+
goal: `Execute ${selected}`,
|
|
99
|
+
steps: [{ type: 'skill', skill: selected, input: context.input.metadata?.skillInput ?? context.input.input }],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
32
103
|
return {
|
|
33
104
|
goal: 'Generate a response',
|
|
34
105
|
steps: [{ type: 'model', task: 'generate_response', input: context.input.input }],
|
|
35
106
|
};
|
|
36
107
|
}
|
|
37
108
|
}
|
|
109
|
+
export class ModelSkillPlanner extends DefaultPlanner {
|
|
110
|
+
constructor(model) {
|
|
111
|
+
super({ model, modelDrivenSkillSelection: true });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
38
114
|
export class DefaultExecutor {
|
|
39
115
|
async execute(plan, context) {
|
|
40
116
|
let lastOutput;
|
|
@@ -44,28 +120,24 @@ export class DefaultExecutor {
|
|
|
44
120
|
}
|
|
45
121
|
else if (step.type === 'skill') {
|
|
46
122
|
await context.trace.append({ type: 'skill.start', data: { name: step.skill } });
|
|
47
|
-
await context.emitProgress({ type: 'skill.started', message:
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
metadata: context.input.metadata,
|
|
53
|
-
emitProgress: context.emitProgress,
|
|
54
|
-
});
|
|
55
|
-
await context.emitProgress({ type: 'skill.completed', message: `执行 ${step.skill} 完成.`, data: { skill: step.skill } });
|
|
123
|
+
await context.emitProgress({ type: 'skill.started', message: `Executing ${step.skill}.`, data: { skill: step.skill } });
|
|
124
|
+
const skill = context.skills.get(step.skill);
|
|
125
|
+
const data = await executeSkillWithPolicy(skill, step.input, context);
|
|
126
|
+
await context.emitProgress({ type: 'skill.completed', message: `Finished ${step.skill}.`, data: { skill: step.skill } });
|
|
127
|
+
await context.trace.append({ type: 'skill.end', data: { name: step.skill } });
|
|
56
128
|
lastOutput = {
|
|
57
129
|
success: true,
|
|
58
130
|
intent: 'skill',
|
|
59
|
-
message: `${step.skill}
|
|
131
|
+
message: `${step.skill} completed.`,
|
|
60
132
|
data,
|
|
61
133
|
traceId: context.traceId,
|
|
62
134
|
};
|
|
63
135
|
}
|
|
64
136
|
else {
|
|
65
137
|
const memory = context.input.sessionId ? await context.memory.get(context.input.sessionId) : [];
|
|
66
|
-
await context.emitProgress({ type: 'model.started', message: '
|
|
138
|
+
await context.emitProgress({ type: 'model.started', message: 'Calling model.', data: { task: step.task } });
|
|
67
139
|
const result = await context.model.generate({ task: step.task, input: step.input, memory });
|
|
68
|
-
await context.emitProgress({ type: 'model.completed', message: '
|
|
140
|
+
await context.emitProgress({ type: 'model.completed', message: 'Model call completed.', data: { task: step.task } });
|
|
69
141
|
lastOutput = {
|
|
70
142
|
success: true,
|
|
71
143
|
intent: step.task,
|
|
@@ -78,7 +150,8 @@ export class DefaultExecutor {
|
|
|
78
150
|
return lastOutput ?? {
|
|
79
151
|
success: false,
|
|
80
152
|
intent: 'empty_plan',
|
|
81
|
-
message: '
|
|
153
|
+
message: 'No executable plan steps.',
|
|
154
|
+
errorDetails: [{ code: 'empty_plan', message: 'No executable plan steps.' }],
|
|
82
155
|
traceId: context.traceId,
|
|
83
156
|
};
|
|
84
157
|
}
|
|
@@ -91,6 +164,57 @@ export class NoopTraceProvider {
|
|
|
91
164
|
};
|
|
92
165
|
}
|
|
93
166
|
}
|
|
167
|
+
export class ConsoleTraceProvider {
|
|
168
|
+
logger;
|
|
169
|
+
constructor(logger = console) {
|
|
170
|
+
this.logger = logger;
|
|
171
|
+
}
|
|
172
|
+
start(input, traceId) {
|
|
173
|
+
this.logger.log(`[${traceId}] trace.start`, { input: input.input });
|
|
174
|
+
return {
|
|
175
|
+
append: (event) => {
|
|
176
|
+
this.logger.log(`[${traceId}] ${event.type}`, event.data);
|
|
177
|
+
},
|
|
178
|
+
end: (output) => {
|
|
179
|
+
const log = output.success ? this.logger.log : this.logger.error;
|
|
180
|
+
log.call(this.logger, `[${traceId}] trace.end`, output);
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
export class InMemoryTraceProvider {
|
|
186
|
+
runs = new Map();
|
|
187
|
+
start(input, traceId) {
|
|
188
|
+
const run = {
|
|
189
|
+
traceId,
|
|
190
|
+
input,
|
|
191
|
+
events: [],
|
|
192
|
+
startedAt: new Date().toISOString(),
|
|
193
|
+
};
|
|
194
|
+
this.runs.set(traceId, run);
|
|
195
|
+
return {
|
|
196
|
+
append: (event) => {
|
|
197
|
+
run.events.push({ ...event, at: new Date().toISOString() });
|
|
198
|
+
},
|
|
199
|
+
end: (output) => {
|
|
200
|
+
run.output = output;
|
|
201
|
+
run.endedAt = new Date().toISOString();
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
get(traceId) {
|
|
206
|
+
return this.runs.get(traceId);
|
|
207
|
+
}
|
|
208
|
+
list() {
|
|
209
|
+
return [...this.runs.values()];
|
|
210
|
+
}
|
|
211
|
+
clear(traceId) {
|
|
212
|
+
if (traceId)
|
|
213
|
+
this.runs.delete(traceId);
|
|
214
|
+
else
|
|
215
|
+
this.runs.clear();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
94
218
|
function findRequestedSkill(context) {
|
|
95
219
|
const explicit = context.input.metadata?.skill;
|
|
96
220
|
if (typeof explicit === 'string' && context.availableSkills.some((skill) => skill.name === explicit))
|
|
@@ -99,3 +223,84 @@ function findRequestedSkill(context) {
|
|
|
99
223
|
return context.availableSkills[0]?.name;
|
|
100
224
|
return context.availableSkills.find((skill) => context.input.input.startsWith(`${skill.name}:`))?.name;
|
|
101
225
|
}
|
|
226
|
+
async function selectSkillWithModel(model, context) {
|
|
227
|
+
const skills = context.availableSkills.map((skill) => ({
|
|
228
|
+
name: skill.name,
|
|
229
|
+
description: skill.description,
|
|
230
|
+
}));
|
|
231
|
+
const result = await model.generate({
|
|
232
|
+
task: 'select_skill',
|
|
233
|
+
input: {
|
|
234
|
+
instruction: 'Choose the best skill for the user input. Return only a skill name, or "none".',
|
|
235
|
+
userInput: context.input.input,
|
|
236
|
+
skills,
|
|
237
|
+
},
|
|
238
|
+
memory: context.memory,
|
|
239
|
+
});
|
|
240
|
+
const selected = result.text.trim().replace(/^["']|["']$/g, '');
|
|
241
|
+
if (!selected || selected.toLowerCase() === 'none')
|
|
242
|
+
return undefined;
|
|
243
|
+
return context.availableSkills.some((skill) => skill.name === selected) ? selected : undefined;
|
|
244
|
+
}
|
|
245
|
+
async function executeSkillWithPolicy(skill, input, context) {
|
|
246
|
+
const maxAttempts = Math.max(1, (skill.retry ?? 0) + 1);
|
|
247
|
+
let lastError;
|
|
248
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
249
|
+
try {
|
|
250
|
+
return await withTimeout(context.skills.execute(skill.name, input, {
|
|
251
|
+
traceId: context.traceId,
|
|
252
|
+
sessionId: context.input.sessionId,
|
|
253
|
+
userId: context.input.userId,
|
|
254
|
+
metadata: context.input.metadata,
|
|
255
|
+
webhook: context.webhook,
|
|
256
|
+
trace: context.trace,
|
|
257
|
+
emitProgress: context.emitProgress,
|
|
258
|
+
}), skill.timeoutMs, `skill_timeout: ${skill.name}`);
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
lastError = error;
|
|
262
|
+
await context.trace.append({
|
|
263
|
+
type: 'skill.error',
|
|
264
|
+
data: { name: skill.name, attempt, error: errorToAgentError(error) },
|
|
265
|
+
});
|
|
266
|
+
if (attempt === maxAttempts)
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
271
|
+
}
|
|
272
|
+
async function withTimeout(promise, timeoutMs, message) {
|
|
273
|
+
if (!timeoutMs || timeoutMs <= 0)
|
|
274
|
+
return promise;
|
|
275
|
+
let timer;
|
|
276
|
+
try {
|
|
277
|
+
return await Promise.race([
|
|
278
|
+
promise,
|
|
279
|
+
new Promise((_resolve, reject) => {
|
|
280
|
+
timer = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
281
|
+
}),
|
|
282
|
+
]);
|
|
283
|
+
}
|
|
284
|
+
finally {
|
|
285
|
+
if (timer)
|
|
286
|
+
clearTimeout(timer);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function matchesAny(input, patterns) {
|
|
290
|
+
return patterns.some((pattern) => typeof pattern === 'string' ? input.includes(pattern) : pattern.test(input));
|
|
291
|
+
}
|
|
292
|
+
function errorToAgentError(error) {
|
|
293
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
294
|
+
return { code: inferErrorCode(message), message };
|
|
295
|
+
}
|
|
296
|
+
function inferErrorCode(message) {
|
|
297
|
+
if (message.startsWith('skill_timeout'))
|
|
298
|
+
return 'skill_timeout';
|
|
299
|
+
if (message.startsWith('skill_not_found'))
|
|
300
|
+
return 'skill_not_found';
|
|
301
|
+
if (message.startsWith('skill_input_invalid'))
|
|
302
|
+
return 'skill_input_invalid';
|
|
303
|
+
if (message.startsWith('skill_output_invalid'))
|
|
304
|
+
return 'skill_output_invalid';
|
|
305
|
+
return 'skill_execution_failed';
|
|
306
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { AgentBuilder, createAgent } from './runtime.js';
|
|
2
|
-
export { DefaultExecutor, DefaultGuard, DefaultPlanner, InMemoryProvider, NoopTraceProvider } from './defaults.js';
|
|
2
|
+
export { BasicGuard, ConsoleTraceProvider, DefaultExecutor, DefaultGuard, DefaultPlanner, InMemoryProvider, InMemoryTraceProvider, ModelSkillPlanner, NoopTraceProvider, } from './defaults.js';
|
|
3
3
|
export { createOpenAICompatibleProvider, normalizeModelConfig } from './openAICompatibleProvider.js';
|
|
4
|
+
export { HttpWebhookService, NoopWebhookService, buildWebhookPayload, createWebhookService, createWebhookSkill, notifyWebhook, sendWebhook, } from './skills/webhook.js';
|
|
5
|
+
export type { WebhookConfig, WebhookDeliveryResult, WebhookEvent, WebhookPayload, WebhookService, } from './skills/webhook.js';
|
|
4
6
|
export { SkillRegistry, ToolRegistry, toolToSkill } from './skillRegistry.js';
|
|
5
|
-
export type { Agent, AgentContext, AgentInput, AgentOutput, AgentPlan, AgentPlanStep, AgentProgressEvent, AgentProgressHandler, CreateAgentOptions, Executor, ExecutorContext, Guard, GuardResult, MemoryMessage, MemoryProvider, ModelGenerateInput, ModelGenerateOutput, ModelProvider, OpenAICompatibleModelConfig, Planner, Skill, SkillContext, SkillRegistryLike, ToolDefinition, ToolRegistryLike, TraceEvent, TraceProvider, TraceRun, } from './types.js';
|
|
7
|
+
export type { Agent, AgentContext, AgentError, AgentErrorCode, AgentInput, AgentOutput, AgentPlan, AgentPlanStep, AgentProgressEvent, AgentProgressHandler, CreateAgentOptions, Executor, ExecutorContext, Guard, GuardResult, MemoryMessage, MemoryProvider, ModelGenerateInput, ModelGenerateOutput, ModelProvider, ModelUsage, OpenAICompatibleModelConfig, Planner, Skill, SkillContext, SkillRegistryLike, ToolDefinition, ToolRegistryLike, TraceEvent, TraceProvider, TraceRun, } from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { AgentBuilder, createAgent } from './runtime.js';
|
|
2
|
-
export { DefaultExecutor, DefaultGuard, DefaultPlanner, InMemoryProvider, NoopTraceProvider } from './defaults.js';
|
|
2
|
+
export { BasicGuard, ConsoleTraceProvider, DefaultExecutor, DefaultGuard, DefaultPlanner, InMemoryProvider, InMemoryTraceProvider, ModelSkillPlanner, NoopTraceProvider, } from './defaults.js';
|
|
3
3
|
export { createOpenAICompatibleProvider, normalizeModelConfig } from './openAICompatibleProvider.js';
|
|
4
|
+
export { HttpWebhookService, NoopWebhookService, buildWebhookPayload, createWebhookService, createWebhookSkill, notifyWebhook, sendWebhook, } from './skills/webhook.js';
|
|
4
5
|
export { SkillRegistry, ToolRegistry, toolToSkill } from './skillRegistry.js';
|
|
@@ -3,7 +3,11 @@ export declare const DEFAULT_MODEL = "gpt-4o-mini";
|
|
|
3
3
|
export interface OpenAICompatibleProviderOptions {
|
|
4
4
|
fetch?: typeof globalThis.fetch;
|
|
5
5
|
}
|
|
6
|
-
export declare function normalizeModelConfig(config: OpenAICompatibleModelConfig): Required<Omit<OpenAICompatibleModelConfig, 'headers'>> & {
|
|
6
|
+
export declare function normalizeModelConfig(config: OpenAICompatibleModelConfig): Required<Omit<OpenAICompatibleModelConfig, 'headers' | 'systemPrompt' | 'temperature' | 'maxTokens' | 'responseFormat'>> & {
|
|
7
7
|
headers: Record<string, string>;
|
|
8
|
+
systemPrompt?: string;
|
|
9
|
+
temperature?: number;
|
|
10
|
+
maxTokens?: number;
|
|
11
|
+
responseFormat?: 'text' | 'json_object' | Record<string, unknown>;
|
|
8
12
|
};
|
|
9
13
|
export declare function createOpenAICompatibleProvider(modelConfig: OpenAICompatibleModelConfig, options?: OpenAICompatibleProviderOptions): ModelProvider;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
2
2
|
const DEFAULT_MAX_RETRIES = 1;
|
|
3
|
+
const DEFAULT_RETRY_BACKOFF_MS = 250;
|
|
3
4
|
export const DEFAULT_MODEL = 'gpt-4o-mini';
|
|
4
5
|
export function normalizeModelConfig(config) {
|
|
5
6
|
const baseUrl = requireValue(config.baseUrl, 'model.baseUrl').replace(/\/+$/, '');
|
|
@@ -11,7 +12,12 @@ export function normalizeModelConfig(config) {
|
|
|
11
12
|
model,
|
|
12
13
|
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
13
14
|
maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
15
|
+
retryBackoffMs: config.retryBackoffMs ?? DEFAULT_RETRY_BACKOFF_MS,
|
|
14
16
|
headers: { ...(config.headers ?? {}) },
|
|
17
|
+
...(config.systemPrompt ? { systemPrompt: config.systemPrompt } : {}),
|
|
18
|
+
...(config.temperature !== undefined ? { temperature: config.temperature } : {}),
|
|
19
|
+
...(config.maxTokens !== undefined ? { maxTokens: config.maxTokens } : {}),
|
|
20
|
+
...(config.responseFormat !== undefined ? { responseFormat: config.responseFormat } : {}),
|
|
15
21
|
};
|
|
16
22
|
}
|
|
17
23
|
export function createOpenAICompatibleProvider(modelConfig, options = {}) {
|
|
@@ -33,27 +39,36 @@ export function createOpenAICompatibleProvider(modelConfig, options = {}) {
|
|
|
33
39
|
authorization: `Bearer ${config.apiKey}`,
|
|
34
40
|
...config.headers,
|
|
35
41
|
},
|
|
36
|
-
body: JSON.stringify(
|
|
37
|
-
model: config.model,
|
|
38
|
-
messages: [
|
|
39
|
-
...input.memory.map((message) => ({ role: message.role, content: message.content })),
|
|
40
|
-
{ role: 'user', content: stringifyInput(input.input) },
|
|
41
|
-
],
|
|
42
|
-
}),
|
|
42
|
+
body: JSON.stringify(buildRequestBody(config, input)),
|
|
43
43
|
signal: controller.signal,
|
|
44
44
|
});
|
|
45
|
-
if (!response.ok)
|
|
46
|
-
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
const message = `model_request_failed: ${response.status} ${await response.text()}`;
|
|
47
|
+
throw new ModelRequestError(message, response.status, isRetryableStatus(response.status));
|
|
48
|
+
}
|
|
47
49
|
const payload = await response.json();
|
|
48
50
|
const text = payload.choices?.[0]?.message?.content;
|
|
49
51
|
if (!text)
|
|
50
52
|
throw new Error('model_response_invalid: choices[0].message.content is missing');
|
|
51
|
-
return {
|
|
53
|
+
return {
|
|
54
|
+
text,
|
|
55
|
+
...(payload.usage ? {
|
|
56
|
+
usage: {
|
|
57
|
+
promptTokens: payload.usage.prompt_tokens,
|
|
58
|
+
completionTokens: payload.usage.completion_tokens,
|
|
59
|
+
totalTokens: payload.usage.total_tokens,
|
|
60
|
+
raw: payload.usage,
|
|
61
|
+
},
|
|
62
|
+
} : {}),
|
|
63
|
+
};
|
|
52
64
|
}
|
|
53
65
|
catch (error) {
|
|
54
66
|
lastError = error;
|
|
55
67
|
if (attempt === config.maxRetries)
|
|
56
68
|
break;
|
|
69
|
+
if (!isRetryableError(error))
|
|
70
|
+
break;
|
|
71
|
+
await sleep(config.retryBackoffMs * 2 ** attempt);
|
|
57
72
|
}
|
|
58
73
|
finally {
|
|
59
74
|
clearTimeout(timer);
|
|
@@ -72,3 +87,47 @@ function requireValue(value, field) {
|
|
|
72
87
|
function stringifyInput(input) {
|
|
73
88
|
return typeof input === 'string' ? input : JSON.stringify(input);
|
|
74
89
|
}
|
|
90
|
+
function buildRequestBody(config, input) {
|
|
91
|
+
return {
|
|
92
|
+
model: config.model,
|
|
93
|
+
messages: [
|
|
94
|
+
...(config.systemPrompt ? [{ role: 'system', content: config.systemPrompt }] : []),
|
|
95
|
+
...input.memory.map((message) => ({ role: message.role, content: message.content })),
|
|
96
|
+
{ role: 'user', content: stringifyInput(input.input) },
|
|
97
|
+
],
|
|
98
|
+
...(config.temperature !== undefined ? { temperature: config.temperature } : {}),
|
|
99
|
+
...(config.maxTokens !== undefined ? { max_tokens: config.maxTokens } : {}),
|
|
100
|
+
...(config.responseFormat !== undefined ? { response_format: normalizeResponseFormat(config.responseFormat) } : {}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function normalizeResponseFormat(responseFormat) {
|
|
104
|
+
if (responseFormat === 'text')
|
|
105
|
+
return { type: 'text' };
|
|
106
|
+
if (responseFormat === 'json_object')
|
|
107
|
+
return { type: 'json_object' };
|
|
108
|
+
return responseFormat;
|
|
109
|
+
}
|
|
110
|
+
function isRetryableStatus(status) {
|
|
111
|
+
return status === 408 || status === 409 || status === 425 || status === 429 || status >= 500;
|
|
112
|
+
}
|
|
113
|
+
function isRetryableError(error) {
|
|
114
|
+
if (error instanceof ModelRequestError)
|
|
115
|
+
return error.retryable;
|
|
116
|
+
if (error instanceof DOMException && error.name === 'AbortError')
|
|
117
|
+
return true;
|
|
118
|
+
return !(error instanceof Error) || !error.message.startsWith('model_response_invalid');
|
|
119
|
+
}
|
|
120
|
+
function sleep(ms) {
|
|
121
|
+
if (ms <= 0)
|
|
122
|
+
return Promise.resolve();
|
|
123
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
124
|
+
}
|
|
125
|
+
class ModelRequestError extends Error {
|
|
126
|
+
status;
|
|
127
|
+
retryable;
|
|
128
|
+
constructor(message, status, retryable) {
|
|
129
|
+
super(message);
|
|
130
|
+
this.status = status;
|
|
131
|
+
this.retryable = retryable;
|
|
132
|
+
}
|
|
133
|
+
}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type WebhookService } from './skills/webhook.js';
|
|
1
2
|
import type { Agent, CreateAgentOptions, Executor, Guard, MemoryProvider, ModelProvider, Planner, Skill, TraceProvider } from './types.js';
|
|
2
3
|
export declare class AgentBuilder {
|
|
3
4
|
private readonly skills;
|
|
@@ -7,6 +8,7 @@ export declare class AgentBuilder {
|
|
|
7
8
|
private model;
|
|
8
9
|
private guard;
|
|
9
10
|
private trace;
|
|
11
|
+
private webhook;
|
|
10
12
|
constructor(options: CreateAgentOptions);
|
|
11
13
|
useSkill(skill: Skill): this;
|
|
12
14
|
useMemory(memory: MemoryProvider): this;
|
|
@@ -15,6 +17,7 @@ export declare class AgentBuilder {
|
|
|
15
17
|
useModel(model: ModelProvider): this;
|
|
16
18
|
useGuard(guard: Guard): this;
|
|
17
19
|
useTrace(trace: TraceProvider): this;
|
|
20
|
+
useWebhook(webhook: WebhookService): this;
|
|
18
21
|
build(): Agent;
|
|
19
22
|
}
|
|
20
23
|
export declare function createAgent(options: CreateAgentOptions): AgentBuilder;
|
package/dist/runtime.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DefaultExecutor, DefaultGuard, DefaultPlanner, InMemoryProvider, NoopTraceProvider } from './defaults.js';
|
|
2
2
|
import { createOpenAICompatibleProvider, normalizeModelConfig } from './openAICompatibleProvider.js';
|
|
3
|
+
import { createWebhookService, NoopWebhookService } from './skills/webhook.js';
|
|
3
4
|
import { SkillRegistry, toolToSkill } from './skillRegistry.js';
|
|
4
5
|
export class AgentBuilder {
|
|
5
6
|
skills = new SkillRegistry();
|
|
@@ -9,9 +10,11 @@ export class AgentBuilder {
|
|
|
9
10
|
model;
|
|
10
11
|
guard = new DefaultGuard();
|
|
11
12
|
trace = new NoopTraceProvider();
|
|
13
|
+
webhook;
|
|
12
14
|
constructor(options) {
|
|
13
15
|
const modelConfig = normalizeModelConfig(options.model);
|
|
14
16
|
this.model = createOpenAICompatibleProvider(modelConfig);
|
|
17
|
+
this.webhook = options.webhook ? createWebhookService(options.webhook) : new NoopWebhookService();
|
|
15
18
|
for (const tool of options.tools ?? [])
|
|
16
19
|
this.skills.register(toolToSkill(tool));
|
|
17
20
|
for (const tool of options.toolRegistry?.listTools() ?? []) {
|
|
@@ -46,6 +49,10 @@ export class AgentBuilder {
|
|
|
46
49
|
this.trace = trace;
|
|
47
50
|
return this;
|
|
48
51
|
}
|
|
52
|
+
useWebhook(webhook) {
|
|
53
|
+
this.webhook = webhook;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
49
56
|
build() {
|
|
50
57
|
assertFunction(this.memory, 'get', 'memory');
|
|
51
58
|
assertFunction(this.memory, 'append', 'memory');
|
|
@@ -54,6 +61,7 @@ export class AgentBuilder {
|
|
|
54
61
|
assertFunction(this.model, 'generate', 'model');
|
|
55
62
|
assertFunction(this.guard, 'check', 'guard');
|
|
56
63
|
assertFunction(this.trace, 'start', 'trace');
|
|
64
|
+
assertFunction(this.webhook, 'notify', 'webhook');
|
|
57
65
|
const runtime = {
|
|
58
66
|
skills: this.skills,
|
|
59
67
|
memory: this.memory,
|
|
@@ -62,6 +70,7 @@ export class AgentBuilder {
|
|
|
62
70
|
model: this.model,
|
|
63
71
|
guard: this.guard,
|
|
64
72
|
trace: this.trace,
|
|
73
|
+
webhook: this.webhook,
|
|
65
74
|
};
|
|
66
75
|
return {
|
|
67
76
|
async run(input) {
|
|
@@ -71,20 +80,29 @@ export class AgentBuilder {
|
|
|
71
80
|
const traceRun = runtime.trace.start(input, traceId);
|
|
72
81
|
const progress = progressEmitter(input, traceId);
|
|
73
82
|
try {
|
|
74
|
-
await progress({ type: 'agent.started', message: '
|
|
83
|
+
await progress({ type: 'agent.started', message: 'Agent started.', data: { input: input.input } });
|
|
75
84
|
const memory = input.sessionId ? await runtime.memory.get(input.sessionId) : [];
|
|
76
85
|
const context = {
|
|
77
86
|
input,
|
|
78
87
|
memory,
|
|
79
|
-
availableSkills: runtime.skills.list().map(({ name, description }) => ({
|
|
88
|
+
availableSkills: runtime.skills.list().map(({ name, description, inputSchema, permission, tags }) => ({
|
|
89
|
+
name,
|
|
90
|
+
description,
|
|
91
|
+
inputSchema,
|
|
92
|
+
permission,
|
|
93
|
+
tags,
|
|
94
|
+
})),
|
|
95
|
+
webhook: runtime.webhook,
|
|
96
|
+
trace: traceRun,
|
|
80
97
|
};
|
|
81
|
-
await progress({ type: 'guard.started', message: '
|
|
98
|
+
await progress({ type: 'guard.started', message: 'Checking guard.' });
|
|
82
99
|
const guardResult = await runtime.guard.check(context);
|
|
83
100
|
if (!guardResult.allowed) {
|
|
84
101
|
const output = {
|
|
85
102
|
success: false,
|
|
86
103
|
intent: 'safe_redirect',
|
|
87
104
|
message: guardResult.reason ?? 'The request was blocked by the guard.',
|
|
105
|
+
errorDetails: [{ code: 'guard_blocked', message: guardResult.reason ?? 'The request was blocked by the guard.' }],
|
|
88
106
|
traceId,
|
|
89
107
|
};
|
|
90
108
|
await progress({ type: 'guard.blocked', message: output.message });
|
|
@@ -92,14 +110,14 @@ export class AgentBuilder {
|
|
|
92
110
|
await progress({ type: 'agent.completed', message: 'Agent completed.', data: output });
|
|
93
111
|
return output;
|
|
94
112
|
}
|
|
95
|
-
await progress({ type: 'guard.completed', message: '
|
|
113
|
+
await progress({ type: 'guard.completed', message: 'Guard passed.' });
|
|
96
114
|
if (input.sessionId) {
|
|
97
115
|
await runtime.memory.append(input.sessionId, { role: 'user', content: input.input, at: new Date().toISOString() });
|
|
98
116
|
}
|
|
99
|
-
await progress({ type: 'planner.started', message: '
|
|
117
|
+
await progress({ type: 'planner.started', message: 'Planning next steps.' });
|
|
100
118
|
const plan = await runtime.planner.plan(context);
|
|
101
119
|
await traceRun.append({ type: 'plan.created', data: plan });
|
|
102
|
-
await progress({ type: 'plan.created', message: '
|
|
120
|
+
await progress({ type: 'plan.created', message: 'Plan created.', data: plan });
|
|
103
121
|
const output = await runtime.executor.execute(plan, {
|
|
104
122
|
input,
|
|
105
123
|
traceId,
|
|
@@ -107,6 +125,7 @@ export class AgentBuilder {
|
|
|
107
125
|
model: runtime.model,
|
|
108
126
|
skills: runtime.skills,
|
|
109
127
|
trace: traceRun,
|
|
128
|
+
webhook: runtime.webhook,
|
|
110
129
|
emitProgress: progress,
|
|
111
130
|
});
|
|
112
131
|
if (input.sessionId) {
|
|
@@ -118,17 +137,20 @@ export class AgentBuilder {
|
|
|
118
137
|
});
|
|
119
138
|
}
|
|
120
139
|
await traceRun.end(output);
|
|
121
|
-
await progress({ type: 'agent.completed', message: '
|
|
140
|
+
await progress({ type: 'agent.completed', message: 'Agent completed.', data: output });
|
|
122
141
|
return output;
|
|
123
142
|
}
|
|
124
143
|
catch (error) {
|
|
144
|
+
const errorDetail = errorToAgentError(error);
|
|
125
145
|
const output = {
|
|
126
146
|
success: false,
|
|
127
147
|
intent: 'runtime_error',
|
|
128
148
|
message: 'Agent execution failed.',
|
|
129
|
-
errors: [
|
|
149
|
+
errors: [errorDetail.message],
|
|
150
|
+
errorDetails: [errorDetail],
|
|
130
151
|
traceId,
|
|
131
152
|
};
|
|
153
|
+
await traceRun.append({ type: 'agent.error', data: errorDetail });
|
|
132
154
|
await traceRun.end(output);
|
|
133
155
|
await progress({ type: 'agent.failed', message: 'Agent execution failed.', data: output });
|
|
134
156
|
return output;
|
|
@@ -150,6 +172,25 @@ function assertFunction(value, key, moduleName) {
|
|
|
150
172
|
function createId(prefix) {
|
|
151
173
|
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
152
174
|
}
|
|
175
|
+
function errorToAgentError(error) {
|
|
176
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
177
|
+
return { code: inferErrorCode(message), message };
|
|
178
|
+
}
|
|
179
|
+
function inferErrorCode(message) {
|
|
180
|
+
if (message.startsWith('skill_timeout'))
|
|
181
|
+
return 'skill_timeout';
|
|
182
|
+
if (message.startsWith('skill_not_found'))
|
|
183
|
+
return 'skill_not_found';
|
|
184
|
+
if (message.startsWith('skill_input_invalid'))
|
|
185
|
+
return 'skill_input_invalid';
|
|
186
|
+
if (message.startsWith('skill_output_invalid'))
|
|
187
|
+
return 'skill_output_invalid';
|
|
188
|
+
if (message.startsWith('model_request_failed'))
|
|
189
|
+
return 'model_request_failed';
|
|
190
|
+
if (message.startsWith('model_response_invalid'))
|
|
191
|
+
return 'model_response_invalid';
|
|
192
|
+
return 'runtime_error';
|
|
193
|
+
}
|
|
153
194
|
function progressEmitter(input, traceId) {
|
|
154
195
|
const handler = input.metadata?.onProgress;
|
|
155
196
|
return async (event) => {
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Skill, TraceRun } from '../types.js';
|
|
3
|
+
export interface WebhookConfig {
|
|
4
|
+
url: string;
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
fetch?: typeof globalThis.fetch;
|
|
8
|
+
warn?: (message: string) => void;
|
|
9
|
+
}
|
|
10
|
+
export type WebhookEvent = 'build.completed' | 'build.failed' | 'directUpload.completed' | 'directUpload.failed';
|
|
11
|
+
export interface WebhookPayload {
|
|
12
|
+
event: WebhookEvent;
|
|
13
|
+
timestamp?: string;
|
|
14
|
+
message: string;
|
|
15
|
+
logs?: string[];
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface WebhookDeliveryResult {
|
|
19
|
+
delivered: boolean;
|
|
20
|
+
status?: number;
|
|
21
|
+
statusText?: string;
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface WebhookService {
|
|
25
|
+
notify(payload: WebhookPayload, trace?: TraceRun): Promise<WebhookDeliveryResult>;
|
|
26
|
+
}
|
|
27
|
+
/** 构造 webhook 推送的 payload。 */
|
|
28
|
+
export declare function buildWebhookPayload(params: {
|
|
29
|
+
event: WebhookEvent;
|
|
30
|
+
message: string;
|
|
31
|
+
logs?: string[];
|
|
32
|
+
error?: string;
|
|
33
|
+
}): WebhookPayload;
|
|
34
|
+
export declare function sendWebhook(url: string, payload: WebhookPayload, timeoutMs?: number, options?: {
|
|
35
|
+
headers?: Record<string, string>;
|
|
36
|
+
fetch?: typeof globalThis.fetch;
|
|
37
|
+
warn?: (message: string) => void;
|
|
38
|
+
}): Promise<WebhookDeliveryResult>;
|
|
39
|
+
export declare function notifyWebhook(config: WebhookConfig | undefined, payload: WebhookPayload): Promise<WebhookDeliveryResult>;
|
|
40
|
+
export declare class NoopWebhookService implements WebhookService {
|
|
41
|
+
notify(): Promise<WebhookDeliveryResult>;
|
|
42
|
+
}
|
|
43
|
+
export declare class HttpWebhookService implements WebhookService {
|
|
44
|
+
private readonly config;
|
|
45
|
+
constructor(config: WebhookConfig);
|
|
46
|
+
notify(payload: WebhookPayload, trace?: TraceRun): Promise<WebhookDeliveryResult>;
|
|
47
|
+
}
|
|
48
|
+
declare const webhookInputSchema: z.ZodObject<{
|
|
49
|
+
event: z.ZodEnum<["build.completed", "build.failed", "directUpload.completed", "directUpload.failed"]>;
|
|
50
|
+
message: z.ZodString;
|
|
51
|
+
logs: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
52
|
+
error: z.ZodOptional<z.ZodString>;
|
|
53
|
+
}, "strip", z.ZodTypeAny, {
|
|
54
|
+
event: "build.completed" | "build.failed" | "directUpload.completed" | "directUpload.failed";
|
|
55
|
+
message: string;
|
|
56
|
+
error?: string | undefined;
|
|
57
|
+
logs?: string[] | undefined;
|
|
58
|
+
}, {
|
|
59
|
+
event: "build.completed" | "build.failed" | "directUpload.completed" | "directUpload.failed";
|
|
60
|
+
message: string;
|
|
61
|
+
error?: string | undefined;
|
|
62
|
+
logs?: string[] | undefined;
|
|
63
|
+
}>;
|
|
64
|
+
declare const webhookOutputSchema: z.ZodObject<{
|
|
65
|
+
ok: z.ZodBoolean;
|
|
66
|
+
delivered: z.ZodBoolean;
|
|
67
|
+
status: z.ZodOptional<z.ZodNumber>;
|
|
68
|
+
statusText: z.ZodOptional<z.ZodString>;
|
|
69
|
+
error: z.ZodOptional<z.ZodString>;
|
|
70
|
+
}, "strip", z.ZodTypeAny, {
|
|
71
|
+
delivered: boolean;
|
|
72
|
+
ok: boolean;
|
|
73
|
+
status?: number | undefined;
|
|
74
|
+
statusText?: string | undefined;
|
|
75
|
+
error?: string | undefined;
|
|
76
|
+
}, {
|
|
77
|
+
delivered: boolean;
|
|
78
|
+
ok: boolean;
|
|
79
|
+
status?: number | undefined;
|
|
80
|
+
statusText?: string | undefined;
|
|
81
|
+
error?: string | undefined;
|
|
82
|
+
}>;
|
|
83
|
+
export declare function createWebhookSkill(configOrService: WebhookConfig | WebhookService): Skill<z.infer<typeof webhookInputSchema>, z.infer<typeof webhookOutputSchema>>;
|
|
84
|
+
export declare function createWebhookService(config?: WebhookConfig): WebhookService;
|
|
85
|
+
export {};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** 构造 webhook 推送的 payload。 */
|
|
3
|
+
export function buildWebhookPayload(params) {
|
|
4
|
+
return {
|
|
5
|
+
event: params.event,
|
|
6
|
+
timestamp: new Date().toISOString(),
|
|
7
|
+
message: params.message,
|
|
8
|
+
logs: params.logs,
|
|
9
|
+
error: params.error,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export async function sendWebhook(url, payload, timeoutMs = 10_000, options = {}) {
|
|
13
|
+
if (!url)
|
|
14
|
+
return { delivered: false };
|
|
15
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
16
|
+
if (!fetchImpl) {
|
|
17
|
+
return warnAndReturn(options.warn, 'Global fetch is unavailable. Node.js 18 or newer is required.');
|
|
18
|
+
}
|
|
19
|
+
const controller = new AbortController();
|
|
20
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
21
|
+
try {
|
|
22
|
+
const response = await fetchImpl(url, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: { 'Content-Type': 'application/json', ...(options.headers ?? {}) },
|
|
25
|
+
body: JSON.stringify(normalizeWebhookPayload(payload)),
|
|
26
|
+
signal: controller.signal,
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
const message = `[webhook] delivery failed: HTTP ${response.status} ${response.statusText}`;
|
|
30
|
+
options.warn?.(message) ?? console.warn(message);
|
|
31
|
+
return { delivered: false, status: response.status, statusText: response.statusText };
|
|
32
|
+
}
|
|
33
|
+
return { delivered: true, status: response.status, statusText: response.statusText };
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
37
|
+
return warnAndReturn(options.warn, `[webhook] delivery error: ${reason}`, reason);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function notifyWebhook(config, payload) {
|
|
44
|
+
if (!config?.url)
|
|
45
|
+
return { delivered: false };
|
|
46
|
+
return await sendWebhook(config.url, payload, config.timeoutMs, config);
|
|
47
|
+
}
|
|
48
|
+
export class NoopWebhookService {
|
|
49
|
+
async notify() {
|
|
50
|
+
return { delivered: false };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export class HttpWebhookService {
|
|
54
|
+
config;
|
|
55
|
+
constructor(config) {
|
|
56
|
+
this.config = config;
|
|
57
|
+
}
|
|
58
|
+
async notify(payload, trace) {
|
|
59
|
+
const startedAt = Date.now();
|
|
60
|
+
const safe = safeWebhookTracePayload(payload);
|
|
61
|
+
await trace?.append({ type: 'webhook.start', data: safe });
|
|
62
|
+
const result = await notifyWebhook(this.config, normalizeWebhookPayload(payload));
|
|
63
|
+
await trace?.append({
|
|
64
|
+
type: result.delivered ? 'webhook.completed' : 'webhook.failed',
|
|
65
|
+
data: {
|
|
66
|
+
...safe,
|
|
67
|
+
delivered: result.delivered,
|
|
68
|
+
status: result.status,
|
|
69
|
+
statusText: result.statusText,
|
|
70
|
+
error: result.error,
|
|
71
|
+
durationMs: Date.now() - startedAt,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const webhookInputSchema = z.object({
|
|
78
|
+
event: z.enum(['build.completed', 'build.failed', 'directUpload.completed', 'directUpload.failed']),
|
|
79
|
+
message: z.string().min(1),
|
|
80
|
+
logs: z.array(z.string()).optional(),
|
|
81
|
+
error: z.string().optional(),
|
|
82
|
+
});
|
|
83
|
+
const webhookOutputSchema = z.object({
|
|
84
|
+
ok: z.boolean(),
|
|
85
|
+
delivered: z.boolean(),
|
|
86
|
+
status: z.number().optional(),
|
|
87
|
+
statusText: z.string().optional(),
|
|
88
|
+
error: z.string().optional(),
|
|
89
|
+
});
|
|
90
|
+
export function createWebhookSkill(configOrService) {
|
|
91
|
+
const service = isWebhookService(configOrService) ? configOrService : new HttpWebhookService(configOrService);
|
|
92
|
+
return {
|
|
93
|
+
name: 'webhook',
|
|
94
|
+
description: 'Send a configured webhook notification.',
|
|
95
|
+
inputSchema: webhookInputSchema,
|
|
96
|
+
outputSchema: webhookOutputSchema,
|
|
97
|
+
permission: 'external_api',
|
|
98
|
+
tags: ['webhook', 'notification'],
|
|
99
|
+
async execute(input, context) {
|
|
100
|
+
const result = await service.notify(buildWebhookPayload(input), context.trace);
|
|
101
|
+
return {
|
|
102
|
+
ok: true,
|
|
103
|
+
delivered: result.delivered,
|
|
104
|
+
status: result.status,
|
|
105
|
+
statusText: result.statusText,
|
|
106
|
+
error: result.error,
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export function createWebhookService(config) {
|
|
112
|
+
return config?.url ? new HttpWebhookService(config) : new NoopWebhookService();
|
|
113
|
+
}
|
|
114
|
+
function normalizeWebhookPayload(payload) {
|
|
115
|
+
return {
|
|
116
|
+
...payload,
|
|
117
|
+
timestamp: payload.timestamp ?? new Date().toISOString(),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function safeWebhookTracePayload(payload) {
|
|
121
|
+
return {
|
|
122
|
+
event: payload.event,
|
|
123
|
+
message: payload.message,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function warnAndReturn(warn, message, error) {
|
|
127
|
+
warn?.(message) ?? console.warn(message);
|
|
128
|
+
return { delivered: false, error: error ?? message };
|
|
129
|
+
}
|
|
130
|
+
function isWebhookService(value) {
|
|
131
|
+
return typeof value.notify === 'function';
|
|
132
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
|
+
import type { WebhookConfig, WebhookService } from './skills/webhook.js';
|
|
2
3
|
export interface OpenAICompatibleModelConfig {
|
|
3
4
|
baseUrl: string;
|
|
4
5
|
apiKey: string;
|
|
5
6
|
model?: string;
|
|
6
7
|
timeoutMs?: number;
|
|
7
8
|
maxRetries?: number;
|
|
9
|
+
retryBackoffMs?: number;
|
|
8
10
|
headers?: Record<string, string>;
|
|
11
|
+
systemPrompt?: string;
|
|
12
|
+
temperature?: number;
|
|
13
|
+
maxTokens?: number;
|
|
14
|
+
responseFormat?: 'text' | 'json_object' | Record<string, unknown>;
|
|
9
15
|
}
|
|
10
16
|
export interface CreateAgentOptions {
|
|
11
17
|
model: OpenAICompatibleModelConfig;
|
|
18
|
+
webhook?: WebhookConfig;
|
|
12
19
|
/** @deprecated Register skills with builder.useSkill(). */
|
|
13
20
|
tools?: ToolDefinition[];
|
|
14
21
|
/** @deprecated Register skills with builder.useSkill(). */
|
|
@@ -35,13 +42,22 @@ export interface AgentOutput {
|
|
|
35
42
|
data?: unknown;
|
|
36
43
|
warnings?: string[];
|
|
37
44
|
errors?: string[];
|
|
45
|
+
errorDetails?: AgentError[];
|
|
38
46
|
traceId?: string;
|
|
39
47
|
}
|
|
48
|
+
export type AgentErrorCode = 'guard_blocked' | 'runtime_error' | 'skill_not_found' | 'skill_input_invalid' | 'skill_output_invalid' | 'skill_timeout' | 'skill_execution_failed' | 'model_request_failed' | 'model_response_invalid' | 'empty_plan';
|
|
49
|
+
export interface AgentError {
|
|
50
|
+
code: AgentErrorCode | string;
|
|
51
|
+
message: string;
|
|
52
|
+
details?: unknown;
|
|
53
|
+
}
|
|
40
54
|
export interface SkillContext {
|
|
41
55
|
traceId: string;
|
|
42
56
|
sessionId?: string;
|
|
43
57
|
userId?: string;
|
|
44
58
|
metadata?: Record<string, unknown>;
|
|
59
|
+
webhook: WebhookService;
|
|
60
|
+
trace: TraceRun;
|
|
45
61
|
emitProgress?(event: Omit<AgentProgressEvent, 'traceId' | 'at'>): Promise<void>;
|
|
46
62
|
}
|
|
47
63
|
export interface Skill<I = unknown, O = unknown> {
|
|
@@ -49,6 +65,10 @@ export interface Skill<I = unknown, O = unknown> {
|
|
|
49
65
|
description: string;
|
|
50
66
|
inputSchema: z.ZodType<I>;
|
|
51
67
|
outputSchema: z.ZodType<O>;
|
|
68
|
+
permission?: 'public' | 'external_api' | 'user_private';
|
|
69
|
+
timeoutMs?: number;
|
|
70
|
+
retry?: number;
|
|
71
|
+
tags?: string[];
|
|
52
72
|
execute(input: I, context: SkillContext): Promise<O>;
|
|
53
73
|
}
|
|
54
74
|
export interface MemoryMessage {
|
|
@@ -70,6 +90,13 @@ export interface ModelGenerateInput {
|
|
|
70
90
|
export interface ModelGenerateOutput {
|
|
71
91
|
text: string;
|
|
72
92
|
data?: unknown;
|
|
93
|
+
usage?: ModelUsage;
|
|
94
|
+
}
|
|
95
|
+
export interface ModelUsage {
|
|
96
|
+
promptTokens?: number;
|
|
97
|
+
completionTokens?: number;
|
|
98
|
+
totalTokens?: number;
|
|
99
|
+
raw?: unknown;
|
|
73
100
|
}
|
|
74
101
|
export interface ModelProvider {
|
|
75
102
|
generate(input: ModelGenerateInput): Promise<ModelGenerateOutput>;
|
|
@@ -77,7 +104,9 @@ export interface ModelProvider {
|
|
|
77
104
|
export interface AgentContext {
|
|
78
105
|
input: AgentInput;
|
|
79
106
|
memory: MemoryMessage[];
|
|
80
|
-
availableSkills: ReadonlyArray<Pick<Skill, 'name' | 'description'>>;
|
|
107
|
+
availableSkills: ReadonlyArray<Pick<Skill, 'name' | 'description' | 'inputSchema' | 'permission' | 'tags'>>;
|
|
108
|
+
webhook: WebhookService;
|
|
109
|
+
trace: TraceRun;
|
|
81
110
|
}
|
|
82
111
|
export interface AgentPlan {
|
|
83
112
|
goal: string;
|
|
@@ -106,6 +135,7 @@ export interface ExecutorContext {
|
|
|
106
135
|
model: ModelProvider;
|
|
107
136
|
skills: SkillRegistryLike;
|
|
108
137
|
trace: TraceRun;
|
|
138
|
+
webhook: WebhookService;
|
|
109
139
|
emitProgress(event: Omit<AgentProgressEvent, 'traceId' | 'at'>): Promise<void>;
|
|
110
140
|
}
|
|
111
141
|
export interface Executor {
|
package/package.json
CHANGED