@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 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
+ ```
@@ -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 declare class DefaultGuard implements Guard {
9
- check(): {
10
- allowed: true;
11
- };
32
+ export interface DefaultPlannerOptions {
33
+ model?: ModelProvider;
34
+ modelDrivenSkillSelection?: boolean;
12
35
  }
13
36
  export declare class DefaultPlanner implements Planner {
14
- plan(context: AgentContext): AgentPlan;
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
- const messages = this.sessions.get(sessionId) ?? [];
5
- messages.push(message);
6
- this.sessions.set(sessionId, messages);
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
- return [...(this.sessions.get(sessionId) ?? [])];
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 DefaultGuard {
19
- check() {
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
- plan(context) {
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: `开始执行 ${step.skill}`, data: { skill: step.skill } });
48
- const data = await context.skills.execute(step.skill, step.input, {
49
- traceId: context.traceId,
50
- sessionId: context.input.sessionId,
51
- userId: context.input.userId,
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: '调用大模型分析', data: { task: step.task } });
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: '大模型分析完成', data: { task: step.task } });
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
- throw new Error(`model_request_failed: ${response.status} ${await response.text()}`);
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 { text };
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: '智能体开始工作', data: { input: input.input } });
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 }) => ({ 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: '计划已创建', data: plan });
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: '智能体工作完成', data: output });
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: [error instanceof Error ? error.message : String(error)],
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-creator/core",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Composable Agent runtime with skills, memory, planning, execution, and OpenAI-compatible models.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",