@artinet/fleet 0.1.0 → 0.1.1

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
@@ -1,6 +1,7 @@
1
1
  <p align="center">
2
2
  <a href="https://artinet.io"><img src="https://img.shields.io/badge/website-artinet.io-black" alt="Website"></a>
3
- <a href="https://www.npmjs.com/package/@artinet/fleet"><img src="https://img.shields.io/npm/v/@artinet/fleet?color=black" alt="Downloads"></a>
3
+ <a href="https://www.npmjs.com/package/@artinet/fleet"><img src="https://img.shields.io/npm/v/@artinet/fleet?color=black" alt="Version"></a>
4
+ <a href="https://www.npmjs.com/package/@artinet/fleet"><img src="https://img.shields.io/npm/dt/@artinet/fleet?color=black" alt="Downloads"></a>
4
5
  <a><img src="https://img.shields.io/badge/License-Apache_2.0-black.svg" alt="License"></a>
5
6
  <a href="https://snyk.io/test/npm/@artinet/fleet"><img src="https://snyk.io/test/npm/@artinet/fleet/badge.svg" alt="Known Vulnerabilities"></a>
6
7
  </p>
@@ -8,7 +9,7 @@
8
9
 
9
10
  Deploy AI agents on any infrastructure.
10
11
 
11
- Fleet is a lightweight server framework for hosting [A2A Protocol](https://github.com/google-a2a/A2A) agents with built-in orchestration, tool integration (MCP), and Agent2Agent communication.
12
+ Fleet is a lightweight server framework for hosting agents with built-in orchestration, tool integration (MCP), and Agent2Agent communication.
12
13
 
13
14
  ## Installation
14
15
 
@@ -220,6 +221,37 @@ configure({
220
221
  });
221
222
  ```
222
223
 
224
+ ### Middleware
225
+
226
+ Intercept and transform agent requests and responses by adding `Middleware`:
227
+
228
+ ```typescript
229
+ import { fleet } from "@artinet/fleet/express";
230
+ import { Middleware } from "@artinet/fleet";
231
+
232
+ fleet({
233
+ middleware: new Middleware()
234
+ .request(async ({ request, context }) => {
235
+ // Inspect or transform incoming requests
236
+ console.log("Incoming request:", request);
237
+ return request;
238
+ })
239
+ .response(
240
+ async ({ response, context }) => {
241
+ // Inspect or transform outgoing responses
242
+ console.log("Outgoing response:", response);
243
+ return response;
244
+ },
245
+ // Use a trigger function to determine if the middleware should fire (defaults to `true` for every request/response)
246
+ ({ response, context }) => {
247
+ return true;
248
+ }
249
+ ),
250
+ }).launch(3000);
251
+ ```
252
+
253
+ The middleware chain is composable & sequential; add multiple `request` or `response` handlers as needed. Each handler receives the current request/response and context, and must return the (optionally modified) value.
254
+
223
255
  ## [Docker Configuration](https://github.com/the-artinet-project/artinet/blob/main/fleet/dockerfile)
224
256
 
225
257
  Build the docker image:
@@ -290,6 +322,7 @@ app.listen(3000);
290
322
  | `testPath` | `string` | `"/test"` | Test endpoint |
291
323
  | `inferenceProviderUrl` | `string` | `undefined` | An OpenAI API compatible endpoint |
292
324
  | `load` | `function` | `loadAgent` | Returns an A2A Protocol compliant agent wrapped in the [`@artinet/sdk`](<(https://github.com/the-artinet-project/artinet-sdk)>) |
325
+ | `middleware` | `Middleware` | `undefined` | Request/response interceptors for the agent route |
293
326
 
294
327
  ## API Reference
295
328
 
package/dist/default.js CHANGED
@@ -13,5 +13,5 @@ export const DEFAULTS = {
13
13
  load: loadAgent,
14
14
  invoke: invokeAgent,
15
15
  storage: new InMemoryStore(),
16
- baseUrl: "https://localhost:3000",
16
+ baseUrl: "http://localhost:3000",
17
17
  };
@@ -1,2 +1,3 @@
1
1
  export * from "./create/index.js";
2
2
  export * from "./request/index.js";
3
+ export { Middleware } from "./intercept.js";
@@ -1,2 +1,3 @@
1
1
  export * from "./create/index.js";
2
2
  export * from "./request/index.js";
3
+ export { Middleware } from "./intercept.js";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Copyright 2025 The Artinet Project
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import * as armada from "@artinet/armada";
6
+ import { RequestAgentRoute } from "./request/types/definitions.js";
7
+ import { API } from "@artinet/types";
8
+ export declare class InterceptBuilder<Req extends API.APIRequest = RequestAgentRoute["request"], Res extends API.APIResponse = RequestAgentRoute["response"], Context extends armada.BaseContext = RequestAgentRoute["context"]> {
9
+ private readonly intercepts;
10
+ constructor(intercepts?: armada.Intercept<Req, Res, Req | Res, Context>[]);
11
+ build(): armada.Intercept<Req, Res, Req | Res, Context>[];
12
+ request(action: armada.Intercept<Req, Res, Req, Context>["action"], trigger?: armada.Intercept<Req, Res, Req, Context>["trigger"]): InterceptBuilder<Req, Res, Context>;
13
+ response(action: armada.Intercept<Req, Res, Res, Context>["action"], trigger?: armada.Intercept<Req, Res, Res, Context>["trigger"]): InterceptBuilder<Req, Res, Context>;
14
+ }
15
+ export type Middleware = InterceptBuilder;
16
+ export declare const Middleware: typeof InterceptBuilder;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Copyright 2025 The Artinet Project
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ import * as armada from "@artinet/armada";
6
+ export class InterceptBuilder {
7
+ intercepts;
8
+ constructor(intercepts = []) {
9
+ this.intercepts = intercepts;
10
+ }
11
+ build() {
12
+ return this.intercepts;
13
+ }
14
+ request(action, trigger) {
15
+ this.intercepts.push({
16
+ action,
17
+ trigger: trigger ?? true,
18
+ phase: armada.Phase.REQUEST,
19
+ });
20
+ return new InterceptBuilder(this.intercepts);
21
+ }
22
+ response(action, trigger) {
23
+ this.intercepts.push({
24
+ action,
25
+ trigger: trigger ?? true,
26
+ phase: armada.Phase.RESPONSE,
27
+ });
28
+ return new InterceptBuilder(this.intercepts);
29
+ }
30
+ }
31
+ export const Middleware = InterceptBuilder;
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import * as sdk from "@artinet/sdk";
6
6
  import { assert } from "console";
7
+ import { A2AError } from "@a2a-js/sdk/server";
7
8
  function isAgentMessenger(agent) {
8
9
  return agent instanceof sdk.AgentMessenger;
9
10
  }
@@ -17,9 +18,20 @@ export const invoke = async (type, request, invocable) => {
17
18
  }
18
19
  catch (error) {
19
20
  sdk.logger.error(`invocation error:${request.method}:error:${JSON.stringify(error, null, 2)}`, { error });
21
+ let jsonRPCError;
22
+ if (error instanceof A2AError || error instanceof sdk.SystemError) {
23
+ jsonRPCError = {
24
+ code: error.code,
25
+ message: error.message,
26
+ data: error.data,
27
+ };
28
+ }
29
+ else {
30
+ jsonRPCError = A2AError.internalError(error?.message ?? "Internal error", { cause: error?.stack }).toJSONRPCError();
31
+ }
20
32
  return {
21
33
  type: "error",
22
- error: error,
34
+ error: jsonRPCError,
23
35
  };
24
36
  }
25
37
  if (!result) {
@@ -33,6 +33,7 @@ export const loadAgent = async (config, context, provider = process.env.OPENAI_A
33
33
  context.defaultInstructions ??
34
34
  DEFAULT_INSTRUCTIONS,
35
35
  provider,
36
+ tasks: context.tasks,
36
37
  });
37
38
  if (requiredAgentsNotLoaded(config, context)) {
38
39
  throw sdk.INTERNAL_ERROR({
@@ -3,7 +3,7 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import { RequestAgentRoute, TestAgentRoute } from "./types/definitions.js";
6
- export declare const requestAgent: (request: RequestAgentRoute["request"], context: RequestAgentRoute["context"], requestFn?: RequestAgentRoute["implementation"]) => Promise<RequestAgentRoute["response"]>;
6
+ export declare const requestAgent: (request: RequestAgentRoute["request"], context: RequestAgentRoute["context"], requestFn?: RequestAgentRoute["implementation"], intercepts?: RequestAgentRoute["intercept"][]) => Promise<RequestAgentRoute["response"]>;
7
7
  export declare const RequestAgent: RequestAgentRoute["implementation"];
8
8
  /**
9
9
  * Test agent implementation.
@@ -6,13 +6,14 @@ import * as armada from "@artinet/armada";
6
6
  import { FetchAgent } from "./interceptors/fetch-agent.js";
7
7
  import { GetAgents } from "./interceptors/get-agents.js";
8
8
  import { requestImplementation } from "./implementation/request.js";
9
- export const requestAgent = (request, context, requestFn = requestImplementation) => armada.entry({
9
+ const DEFAULT_INTERCEPTS = [FetchAgent, GetAgents];
10
+ export const requestAgent = (request, context, requestFn = requestImplementation, intercepts = []) => armada.entry({
10
11
  request,
11
12
  implementation: requestFn,
12
- intercepts: [FetchAgent, GetAgents],
13
+ intercepts: [...DEFAULT_INTERCEPTS, ...intercepts],
13
14
  context,
14
15
  });
15
- export const RequestAgent = (request, context) => requestAgent(request, context, requestImplementation);
16
+ export const RequestAgent = (request, context, intercepts = []) => requestAgent(request, context, requestImplementation, intercepts);
16
17
  /**
17
18
  * Test agent implementation.
18
19
  * @note Similar to RequestAgent, but skips the FetchAgent intercept and uses the testInvoke function that returns a stream of updates.
@@ -5,9 +5,10 @@
5
5
  import * as armada from "@artinet/armada";
6
6
  import { AgentConfiguration } from "agent-def";
7
7
  import * as sdk from "@artinet/sdk";
8
+ import { API } from "@artinet/types";
8
9
  import { z } from "zod/v4";
9
10
  import { ResultOrError } from "../../../types.js";
10
- export type AgentRequest = {
11
+ export type AgentRequest = API.APIRequest & {
11
12
  method: string;
12
13
  params: sdk.A2A.RequestParam;
13
14
  };
@@ -16,11 +17,13 @@ export type AgentError = {
16
17
  message: string;
17
18
  data?: unknown;
18
19
  };
19
- export type AgentResponse = ResultOrError<sdk.A2A.ResponseResult | sdk.A2A.AgentCard, AgentError, sdk.A2A.Update>;
20
+ export type AgentResponse = API.APIResponse & ResultOrError<sdk.A2A.ResponseResult | sdk.A2A.AgentCard, AgentError, sdk.A2A.Update>;
20
21
  export type Agent = sdk.Agent | sdk.AgentMessenger;
21
22
  export type loadFunction = (config: AgentConfiguration, context?: RequestContext) => Promise<Agent | undefined>;
22
23
  export type invokeFunction = <Req extends AgentRequest = AgentRequest>(request: Req, agent: Agent, context?: RequestContext) => Promise<AgentResponse | null>;
23
- export interface RequestContext extends armada.StorageContext<typeof armada.StoredAgentSchema>, armada.FindContext<typeof armada.StoredAgentSchema> {
24
+ export interface RequestContext extends armada.StorageContext<typeof armada.StoredAgentSchema>, armada.FindContext<typeof armada.StoredAgentSchema>,
25
+ /**If passing `contexts` to the `orc8` loader, be aware that it requires a Monitored Context Manager */
26
+ Pick<sdk.CreateAgentParams, "contexts" | "tasks"> {
24
27
  agentId: string;
25
28
  headers?: Record<string, string>;
26
29
  agents?: Record<string, Agent>;
@@ -5,9 +5,9 @@
5
5
  import express from "express";
6
6
  import { RequestAgentRoute, RequestContext } from "../../routes/request/index.js";
7
7
  export declare const AGENT_FIELD_NAME = "agentId";
8
- export type handler = (req: express.Request, res: express.Response, next: express.NextFunction, context: RequestContext, request?: RequestAgentRoute["implementation"]) => Promise<void>;
9
- export declare function handle(req: express.Request, res: express.Response, _next: express.NextFunction, context: RequestContext, request?: RequestAgentRoute["implementation"]): Promise<void>;
10
- export declare const factory: (request?: RequestAgentRoute["implementation"]) => handler;
8
+ export type handler = (req: express.Request, res: express.Response, next: express.NextFunction, context: RequestContext, request?: RequestAgentRoute["implementation"], intercepts?: RequestAgentRoute["intercept"][]) => Promise<void>;
9
+ export declare function handle(req: express.Request, res: express.Response, _next: express.NextFunction, context: RequestContext, request?: RequestAgentRoute["implementation"], intercepts?: RequestAgentRoute["intercept"][]): Promise<void>;
10
+ export declare const factory: (request?: RequestAgentRoute["implementation"], intercepts?: RequestAgentRoute["intercept"][]) => handler;
11
11
  /**
12
12
  * Handler utilities for agent HTTP requests.
13
13
  *
@@ -8,7 +8,7 @@ import * as sdk from "@artinet/sdk";
8
8
  import { handleJSONRPCResponse } from "./rpc.js";
9
9
  import { generateRequestId } from "./utils.js";
10
10
  export const AGENT_FIELD_NAME = "agentId";
11
- export async function handle(req, res, _next, context, request = RequestAgent) {
11
+ export async function handle(req, res, _next, context, request = RequestAgent, intercepts) {
12
12
  const requestId = generateRequestId(context, req);
13
13
  let parsed;
14
14
  if (req?.path?.endsWith("agent-card.json") ||
@@ -28,11 +28,12 @@ export async function handle(req, res, _next, context, request = RequestAgent) {
28
28
  method: parsed.method,
29
29
  params: params,
30
30
  };
31
- const response = await request(agentRequest, context);
32
- sdk.logger.info(`handle agent request completed:${parsed.method}:response:${JSON.stringify(response, null, 2)}`);
31
+ sdk.logger.info(`handle agent request received:${parsed.method}:params:${sdk.formatJson(agentRequest)}`);
32
+ const response = await request(agentRequest, context, intercepts);
33
+ sdk.logger.info(`handle agent request completed:${parsed.method}:response:${sdk.formatJson(response)}`);
33
34
  await handleJSONRPCResponse(res, requestId, parsed.method, response);
34
35
  }
35
- export const factory = (request = RequestAgent) => async (req, res, next, context) => await handle(req, res, next, context, request);
36
+ export const factory = (request = RequestAgent, intercepts) => async (req, res, next, context) => await handle(req, res, next, context, request, intercepts);
36
37
  export async function request({ request: req, response: res, next, context, handler = handle, user, }) {
37
38
  const agentId = req?.params?.[AGENT_FIELD_NAME];
38
39
  if (!agentId) {
@@ -4,10 +4,11 @@
4
4
  */
5
5
  import { CreateAgent, CreateAgentRequestSchema, } from "../../routes/create/index.js";
6
6
  import { generateRequestId, generateRegistrationId } from "./utils.js";
7
- import { logger, validateSchema } from "@artinet/sdk";
7
+ import { logger, validateSchema, formatJson } from "@artinet/sdk";
8
8
  export async function handle(req, res, _next, context, deploy = CreateAgent) {
9
- logger.warn(`handle deploy request with body: ${JSON.stringify(req.body)}`);
10
9
  const request = await validateSchema(CreateAgentRequestSchema, req?.body ?? {});
10
+ logger.info(`deploying agent: ${request.config.name}`);
11
+ logger.debug(`deploying agent: ${formatJson(request)}`);
11
12
  context.registrationId = generateRegistrationId(request.config.uri);
12
13
  const result = await deploy(request, context);
13
14
  res.json(result);
@@ -13,7 +13,9 @@ const createContext = (settings) => {
13
13
  const _settings = {
14
14
  ...DEFAULTS,
15
15
  ...settings,
16
- retrieve: agent.factory(settings.get ?? DEFAULTS.get),
16
+ retrieve: agent.factory(settings.get ?? DEFAULTS.get,
17
+ /**Middleware addons are currently only supported on the agent request route */
18
+ settings.middleware?.build() ?? []),
17
19
  deploy: deployment.factory(settings.set ?? DEFAULTS.set),
18
20
  evaluate: testing.factory(settings.test ?? DEFAULTS.test),
19
21
  user: settings.user
@@ -5,9 +5,9 @@
5
5
  import * as hono from "hono";
6
6
  import { RequestAgentRoute, RequestContext } from "../../routes/request/index.js";
7
7
  export declare const AGENT_FIELD_NAME = "agentId";
8
- export type handler = (ctx: hono.Context, next: hono.Next, context: RequestContext, request?: RequestAgentRoute["implementation"]) => Promise<void>;
9
- export declare function handle(ctx: hono.Context, _next: hono.Next, context: RequestContext, request?: RequestAgentRoute["implementation"]): Promise<void>;
10
- export declare const factory: (request?: RequestAgentRoute["implementation"]) => handler;
8
+ export type handler = (ctx: hono.Context, next: hono.Next, context: RequestContext, request?: RequestAgentRoute["implementation"], intercepts?: RequestAgentRoute["intercept"][]) => Promise<void>;
9
+ export declare function handle(ctx: hono.Context, _next: hono.Next, context: RequestContext, request?: RequestAgentRoute["implementation"], intercepts?: RequestAgentRoute["intercept"][]): Promise<void>;
10
+ export declare const factory: (request?: RequestAgentRoute["implementation"], intercepts?: RequestAgentRoute["intercept"][]) => handler;
11
11
  /**
12
12
  * Handler utilities for agent HTTP requests.
13
13
  *
@@ -8,7 +8,7 @@ import * as sdk from "@artinet/sdk";
8
8
  import { handleJSONRPCResponse } from "./rpc.js";
9
9
  import { generateRequestId } from "./utils.js";
10
10
  export const AGENT_FIELD_NAME = "agentId";
11
- export async function handle(ctx, _next, context, request = RequestAgent) {
11
+ export async function handle(ctx, _next, context, request = RequestAgent, intercepts) {
12
12
  /* hono.Context.req uses a raw JSON.parse() so we prefer to use the text() and our own safeParse() */
13
13
  const body = sdk.safeParse(await ctx.req.text());
14
14
  const requestId = generateRequestId(context, ctx.req.header("x-request-id") ?? body?.id);
@@ -30,11 +30,12 @@ export async function handle(ctx, _next, context, request = RequestAgent) {
30
30
  method: parsed.method,
31
31
  params: params,
32
32
  };
33
- const response = await request(agentRequest, context);
34
- sdk.logger.info(`handle agent request completed:${parsed.method}:response:${JSON.stringify(response, null, 2)}`);
33
+ sdk.logger.info(`handle agent request received:${parsed.method}:params:${sdk.formatJson(agentRequest)}`);
34
+ const response = await request(agentRequest, context, intercepts);
35
+ sdk.logger.info(`handle agent request completed:${parsed.method}:response:${sdk.formatJson(response)}`);
35
36
  await handleJSONRPCResponse(ctx, requestId, parsed.method, response);
36
37
  }
37
- export const factory = (request = RequestAgent) => async (ctx, next, context) => await handle(ctx, next, context, request);
38
+ export const factory = (request = RequestAgent, intercepts) => async (ctx, next, context) => await handle(ctx, next, context, request, intercepts);
38
39
  export async function request({ ctx, next, context, handler = handle, user, }) {
39
40
  const agentId = ctx.req.param(AGENT_FIELD_NAME);
40
41
  if (!agentId) {
@@ -8,8 +8,9 @@ import { generateRequestId, generateRegistrationId } from "./utils.js";
8
8
  export async function handle(ctx, _next, context, deploy = CreateAgent) {
9
9
  /* hono.Context.req uses a raw JSON.parse() so we prefer to use the text() and our own safeParse() */
10
10
  const req = sdk.safeParse(await ctx.req.text());
11
- sdk.logger.warn(`handle deploy request with body: ${JSON.stringify(req)}`);
12
11
  const request = await sdk.validateSchema(CreateAgentRequestSchema, req);
12
+ sdk.logger.info(`deploying agent: ${request.config.name}`);
13
+ sdk.logger.debug(`deploying agent: ${sdk.formatJson(request)}`);
13
14
  context.registrationId = generateRegistrationId(request.config.uri);
14
15
  const result = await deploy(request, context);
15
16
  ctx.res = ctx.json(result);
@@ -15,7 +15,9 @@ const createContext = (settings) => {
15
15
  const _settings = {
16
16
  ...DEFAULTS,
17
17
  ...settings,
18
- retrieve: agent.factory(settings.get ?? DEFAULTS.get),
18
+ retrieve: agent.factory(settings.get ?? DEFAULTS.get,
19
+ /**Middleware addons are currently only supported on the agent request route */
20
+ settings.middleware?.build() ?? []),
19
21
  deploy: deployment.factory(settings.set ?? DEFAULTS.set),
20
22
  evaluate: testing.factory(settings.test ?? DEFAULTS.test),
21
23
  user: settings.user
@@ -5,11 +5,14 @@
5
5
  import { RequestAgentRoute, TestAgentRoute } from "./routes/index.js";
6
6
  import { CreateAgentRoute } from "./routes/create/index.js";
7
7
  import { Configuration } from "./types.js";
8
+ import { Middleware } from "./routes/intercept.js";
8
9
  export interface Params extends Configuration {
9
10
  basePath?: string;
10
11
  fallbackPath?: string;
11
12
  deploymentPath?: string;
12
13
  testPath?: string;
14
+ /**Middleware addons are currently only supported on the Request Agent route */
15
+ middleware?: Middleware;
13
16
  }
14
17
  export interface Settings extends Params {
15
18
  get: RequestAgentRoute["implementation"];
@@ -3,7 +3,7 @@
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
5
  import * as armada from "@artinet/armada";
6
- import * as SDK from "@artinet/sdk";
7
- export declare class InMemoryStore extends SDK.Manager<armada.StoredAgent> implements armada.IDataStore<armada.StoredAgent> {
6
+ import * as sdk from "@artinet/sdk";
7
+ export declare class InMemoryStore extends sdk.Manager<armada.StoredAgent> implements armada.IDataStore<armada.StoredAgent> {
8
8
  search(query: string): Promise<armada.StoredAgent[]>;
9
9
  }
@@ -2,8 +2,8 @@
2
2
  * Copyright 2025 The Artinet Project
3
3
  * SPDX-License-Identifier: Apache-2.0
4
4
  */
5
- import * as SDK from "@artinet/sdk";
6
- export class InMemoryStore extends SDK.Manager {
5
+ import * as sdk from "@artinet/sdk";
6
+ export class InMemoryStore extends sdk.Manager {
7
7
  async search(query) {
8
8
  return Array.from(this.data.values()).filter((value) => value.uri.includes(query) ||
9
9
  value.name.includes(query) ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artinet/fleet",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A an agentic orchestration server for on premise deployment.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",