la-machina-engine 0.7.10 → 0.9.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 +54 -1
- package/dist/contract-BY-GqoYk.d.cts +133 -0
- package/dist/contract-BY-GqoYk.d.ts +133 -0
- package/dist/index.cjs +383 -436
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -209
- package/dist/index.d.ts +11 -209
- package/dist/index.js +378 -431
- package/dist/index.js.map +1 -1
- package/dist/node-mcp.cjs +40 -0
- package/dist/node-mcp.cjs.map +1 -0
- package/dist/node-mcp.d.cts +40 -0
- package/dist/node-mcp.d.ts +40 -0
- package/dist/node-mcp.js +15 -0
- package/dist/node-mcp.js.map +1 -0
- package/dist/node-tools.cjs +147 -0
- package/dist/node-tools.cjs.map +1 -0
- package/dist/node-tools.d.cts +38 -0
- package/dist/node-tools.d.ts +38 -0
- package/dist/node-tools.js +110 -0
- package/dist/node-tools.js.map +1 -0
- package/dist/types-Cv2VJSoD.d.cts +113 -0
- package/dist/types-Cv2VJSoD.d.ts +113 -0
- package/package.json +21 -1
package/README.md
CHANGED
|
@@ -73,7 +73,38 @@ npm install la-machina-engine
|
|
|
73
73
|
|
|
74
74
|
Requires Node 20+. Works in Bun, Cloudflare Workers (with R2 storage).
|
|
75
75
|
|
|
76
|
-
The package is published as a single bundled module (~330 KB ESM, ~340 KB CJS) with full TypeScript types. Built with [tsup](https://tsup.egoist.dev/), CI publishes with [Sigstore provenance](https://docs.npmjs.com/generating-provenance-statements).
|
|
76
|
+
The package is published as a single bundled module (~330 KB ESM, ~340 KB CJS) with full TypeScript types plus two Node-only subpaths (`/node-tools`, `/node-mcp`). Built with [tsup](https://tsup.egoist.dev/), CI publishes with [Sigstore provenance](https://docs.npmjs.com/generating-provenance-statements).
|
|
77
|
+
|
|
78
|
+
### 0.7.x → 0.8.0 migration
|
|
79
|
+
|
|
80
|
+
0.8.0 makes the engine fully runtime-agnostic. The default bundle no longer auto-registers `Bash` or auto-stubs MCP stdio servers based on `process.versions.node` probing — the caller now decides which Tools the engine sees per environment. See `plans/031-runtime-agnostic-engine.md` for the full design.
|
|
81
|
+
|
|
82
|
+
Two changes consumers may need:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// Workers callers — Bash via capabilityStub (model sees it; engine
|
|
86
|
+
// pauses with handoff_to_runner when called):
|
|
87
|
+
import { initEngine, capabilityStub } from 'la-machina-engine'
|
|
88
|
+
initEngine({
|
|
89
|
+
tools: { custom: [capabilityStub({ name: 'Bash', description: '...' })] },
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// Node callers — real Bash via the new node-tools subpath:
|
|
93
|
+
import { initEngine } from 'la-machina-engine'
|
|
94
|
+
import { createBashTool } from 'la-machina-engine/node-tools'
|
|
95
|
+
initEngine({ tools: { custom: [createBashTool()] } })
|
|
96
|
+
|
|
97
|
+
// Stdio MCP on Node — pass a transport factory:
|
|
98
|
+
import { createStdioTransportFactory } from 'la-machina-engine/node-mcp'
|
|
99
|
+
initEngine({
|
|
100
|
+
mcp: {
|
|
101
|
+
servers: { local: { type: 'stdio', command: 'npx', args: ['mcp-server-x'], ... } },
|
|
102
|
+
stdioTransport: createStdioTransportFactory(),
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Without `mcp.stdioTransport`, configuring a stdio MCP server fails at connect with a clean error pointing at the subpath (see `plans/031-runtime-agnostic-engine.md`).
|
|
77
108
|
|
|
78
109
|
---
|
|
79
110
|
|
|
@@ -1270,6 +1301,28 @@ model cannot spoof `Authorization` via `input.headers`).
|
|
|
1270
1301
|
dispatch with `{ service, method, path, status, latencyMs, bytesIn }`
|
|
1271
1302
|
— no secrets — for metering, billing, audit logs.
|
|
1272
1303
|
|
|
1304
|
+
**Raw-body uploads (0.9.0):** set `bodyEncoding: 'raw'` on the call to
|
|
1305
|
+
send a string body verbatim — no `JSON.stringify`. Use for upload APIs
|
|
1306
|
+
that take binary or text-blob bodies (Drive media upload, S3
|
|
1307
|
+
PutObject, image push, log ingestion, etc.). The caller-supplied
|
|
1308
|
+
`Content-Type` header wins; default falls back to `text/plain`.
|
|
1309
|
+
|
|
1310
|
+
```ts
|
|
1311
|
+
ApiCall({
|
|
1312
|
+
service: 'gdrive',
|
|
1313
|
+
method: 'PATCH',
|
|
1314
|
+
path: `/upload/drive/v3/files/${fileId}?uploadType=media`,
|
|
1315
|
+
headers: { 'Content-Type': 'text/markdown' },
|
|
1316
|
+
body: '# Hello world',
|
|
1317
|
+
bodyEncoding: 'raw',
|
|
1318
|
+
})
|
|
1319
|
+
```
|
|
1320
|
+
|
|
1321
|
+
`bodyEncoding: 'raw'` requires the body to be a string — passing an
|
|
1322
|
+
object returns `ERR_API_RAW_BODY_NOT_STRING` and never makes the
|
|
1323
|
+
fetch. The default `'json'` encoding behaves exactly as in 0.8.x.
|
|
1324
|
+
`maxBodyBytes` is enforced under both encodings.
|
|
1325
|
+
|
|
1273
1326
|
**Disabling:** `tools.disabled: ['ApiCall']` turns it off even when
|
|
1274
1327
|
services are configured. Absent `config.api` → tool never registered,
|
|
1275
1328
|
no prompt mention.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tool contract — the shape every tool implements, plus a registry to
|
|
5
|
+
* collect them by name.
|
|
6
|
+
*
|
|
7
|
+
* The contract is intentionally minimal:
|
|
8
|
+
*
|
|
9
|
+
* 1. A tool has a `name`, a `description`, and a Zod `inputSchema`.
|
|
10
|
+
* Zod gives both runtime validation and a TypeScript-inferred
|
|
11
|
+
* input type for `execute()`.
|
|
12
|
+
* 2. `execute(input, context)` returns a `ToolResult` (or rejects).
|
|
13
|
+
* The runtime catches rejections and turns them into error
|
|
14
|
+
* results — a misbehaving tool can never crash the agent loop.
|
|
15
|
+
* 3. The runtime supplies a `ToolContext` carrying at least an
|
|
16
|
+
* `AbortSignal` for cancellation.
|
|
17
|
+
*
|
|
18
|
+
* Phase 5 ships the types and the registry. Phase 6 fills the registry
|
|
19
|
+
* with the core tool set (Bash, Read, Write, Edit, Glob, Grep, WebFetch).
|
|
20
|
+
* Custom tools come from `config.tools.custom` at engine init time.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The execution context passed to every tool. Phase 5 keeps this
|
|
25
|
+
* minimal — only an AbortSignal — so the contract stays portable.
|
|
26
|
+
* Later phases extend with storage, run/node ids, hook handles, etc.
|
|
27
|
+
*/
|
|
28
|
+
interface ToolContext {
|
|
29
|
+
readonly signal: AbortSignal;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The result of a single tool invocation. The runtime wraps thrown
|
|
33
|
+
* errors in this shape so the agent loop only ever sees success/error
|
|
34
|
+
* as data, not exceptions.
|
|
35
|
+
*/
|
|
36
|
+
interface ToolResult {
|
|
37
|
+
/** Human- and model-readable output. The agent's next turn sees this. */
|
|
38
|
+
readonly content: string;
|
|
39
|
+
/** True when the tool failed. */
|
|
40
|
+
readonly isError?: boolean;
|
|
41
|
+
/** Optional structured metadata for hooks / observability. */
|
|
42
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A tool definition. Generic on a Zod schema so that `execute()`'s
|
|
46
|
+
* input parameter is statically typed via `z.infer<TSchema>`.
|
|
47
|
+
*
|
|
48
|
+
* Tool authors normally use `defineTool()` to get the inference
|
|
49
|
+
* without writing the generic by hand.
|
|
50
|
+
*/
|
|
51
|
+
interface Tool<TSchema extends z.ZodTypeAny = z.ZodTypeAny> {
|
|
52
|
+
readonly name: string;
|
|
53
|
+
readonly description: string;
|
|
54
|
+
readonly inputSchema: TSchema;
|
|
55
|
+
/**
|
|
56
|
+
* Optional raw JSON Schema to send to the Anthropic API as
|
|
57
|
+
* `input_schema`, bypassing Zod-to-JSON-Schema conversion. Used by
|
|
58
|
+
* MCP-sourced tools whose schema is authored in JSON Schema directly
|
|
59
|
+
* and whose Zod schema is a `z.unknown()` passthrough.
|
|
60
|
+
*
|
|
61
|
+
* When set, `toAnthropicTool()` in the agent loop prefers this over
|
|
62
|
+
* running `zodToJsonSchema(inputSchema)`. Normal in-process tools
|
|
63
|
+
* defined via `defineTool()` should leave this undefined.
|
|
64
|
+
*/
|
|
65
|
+
readonly anthropicSchemaOverride?: Record<string, unknown>;
|
|
66
|
+
/**
|
|
67
|
+
* Returns true if this tool is safe to run concurrently with other
|
|
68
|
+
* concurrency-safe tools. Read-only tools (Read, Glob, Grep, etc.)
|
|
69
|
+
* should return true; mutating tools (Write, Edit, Bash) should
|
|
70
|
+
* return false. Default: false (fail-closed).
|
|
71
|
+
*
|
|
72
|
+
* The agent loop uses this to batch consecutive safe tool calls
|
|
73
|
+
* into a single `Promise.all()` for parallel execution, while
|
|
74
|
+
* running unsafe tools serially.
|
|
75
|
+
*
|
|
76
|
+
* Input is typed as `unknown` (not `z.infer<TSchema>`) so that
|
|
77
|
+
* `Tool<ZodObject<...>>` remains assignable to `Tool<ZodTypeAny>`
|
|
78
|
+
* under `exactOptionalPropertyTypes`.
|
|
79
|
+
*/
|
|
80
|
+
isConcurrencySafe?: ((input: unknown) => boolean) | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Declares that this tool needs Node-only capabilities
|
|
83
|
+
* (`node:child_process`, the real filesystem, stdio-based MCP, etc.)
|
|
84
|
+
* When true AND the runtime can't support them, the engine replaces
|
|
85
|
+
* this tool with a capability stub (see `src/tools/capabilityStub.ts`)
|
|
86
|
+
* that returns a structured `isError` result instead of crashing.
|
|
87
|
+
*
|
|
88
|
+
* Leave undefined / false for tools that work anywhere (Read, Write,
|
|
89
|
+
* WebFetch, http-based MCP). Default: false.
|
|
90
|
+
*/
|
|
91
|
+
readonly requiresNode?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Internal marker set by `capabilityStub()` so the agent-loop's
|
|
94
|
+
* runner-handoff check (Plan 019 §4) can distinguish a stubbed tool
|
|
95
|
+
* from a real one without reflecting on the implementation.
|
|
96
|
+
* Do not set this manually.
|
|
97
|
+
*/
|
|
98
|
+
readonly isCapabilityStub?: boolean;
|
|
99
|
+
execute(input: z.infer<TSchema>, context: ToolContext): Promise<ToolResult>;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Identity helper that locks in the schema generic so callers don't have
|
|
103
|
+
* to write it. Use this whenever defining a tool — it costs nothing at
|
|
104
|
+
* runtime and gives you full inference inside `execute()`.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* const Echo = defineTool({
|
|
109
|
+
* name: 'Echo',
|
|
110
|
+
* description: 'Echoes its input back',
|
|
111
|
+
* inputSchema: z.object({ msg: z.string() }),
|
|
112
|
+
* execute: async ({ msg }) => ({ content: msg }),
|
|
113
|
+
* })
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
declare function defineTool<TSchema extends z.ZodTypeAny>(tool: Tool<TSchema>): Tool<TSchema>;
|
|
117
|
+
/**
|
|
118
|
+
* In-memory registry of tools by name. Used by the engine's tool runtime
|
|
119
|
+
* to dispatch tool calls. Re-registering the same name throws so typos
|
|
120
|
+
* surface immediately rather than silently shadowing.
|
|
121
|
+
*/
|
|
122
|
+
declare class ToolRegistry {
|
|
123
|
+
private readonly tools;
|
|
124
|
+
register(tool: Tool): void;
|
|
125
|
+
registerAll(tools: ReadonlyArray<Tool>): void;
|
|
126
|
+
unregister(name: string): void;
|
|
127
|
+
get(name: string): Tool | undefined;
|
|
128
|
+
has(name: string): boolean;
|
|
129
|
+
list(): Tool[];
|
|
130
|
+
count(): number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { type ToolResult as T, ToolRegistry as a, type Tool as b, type ToolContext as c, defineTool as d };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tool contract — the shape every tool implements, plus a registry to
|
|
5
|
+
* collect them by name.
|
|
6
|
+
*
|
|
7
|
+
* The contract is intentionally minimal:
|
|
8
|
+
*
|
|
9
|
+
* 1. A tool has a `name`, a `description`, and a Zod `inputSchema`.
|
|
10
|
+
* Zod gives both runtime validation and a TypeScript-inferred
|
|
11
|
+
* input type for `execute()`.
|
|
12
|
+
* 2. `execute(input, context)` returns a `ToolResult` (or rejects).
|
|
13
|
+
* The runtime catches rejections and turns them into error
|
|
14
|
+
* results — a misbehaving tool can never crash the agent loop.
|
|
15
|
+
* 3. The runtime supplies a `ToolContext` carrying at least an
|
|
16
|
+
* `AbortSignal` for cancellation.
|
|
17
|
+
*
|
|
18
|
+
* Phase 5 ships the types and the registry. Phase 6 fills the registry
|
|
19
|
+
* with the core tool set (Bash, Read, Write, Edit, Glob, Grep, WebFetch).
|
|
20
|
+
* Custom tools come from `config.tools.custom` at engine init time.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The execution context passed to every tool. Phase 5 keeps this
|
|
25
|
+
* minimal — only an AbortSignal — so the contract stays portable.
|
|
26
|
+
* Later phases extend with storage, run/node ids, hook handles, etc.
|
|
27
|
+
*/
|
|
28
|
+
interface ToolContext {
|
|
29
|
+
readonly signal: AbortSignal;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The result of a single tool invocation. The runtime wraps thrown
|
|
33
|
+
* errors in this shape so the agent loop only ever sees success/error
|
|
34
|
+
* as data, not exceptions.
|
|
35
|
+
*/
|
|
36
|
+
interface ToolResult {
|
|
37
|
+
/** Human- and model-readable output. The agent's next turn sees this. */
|
|
38
|
+
readonly content: string;
|
|
39
|
+
/** True when the tool failed. */
|
|
40
|
+
readonly isError?: boolean;
|
|
41
|
+
/** Optional structured metadata for hooks / observability. */
|
|
42
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A tool definition. Generic on a Zod schema so that `execute()`'s
|
|
46
|
+
* input parameter is statically typed via `z.infer<TSchema>`.
|
|
47
|
+
*
|
|
48
|
+
* Tool authors normally use `defineTool()` to get the inference
|
|
49
|
+
* without writing the generic by hand.
|
|
50
|
+
*/
|
|
51
|
+
interface Tool<TSchema extends z.ZodTypeAny = z.ZodTypeAny> {
|
|
52
|
+
readonly name: string;
|
|
53
|
+
readonly description: string;
|
|
54
|
+
readonly inputSchema: TSchema;
|
|
55
|
+
/**
|
|
56
|
+
* Optional raw JSON Schema to send to the Anthropic API as
|
|
57
|
+
* `input_schema`, bypassing Zod-to-JSON-Schema conversion. Used by
|
|
58
|
+
* MCP-sourced tools whose schema is authored in JSON Schema directly
|
|
59
|
+
* and whose Zod schema is a `z.unknown()` passthrough.
|
|
60
|
+
*
|
|
61
|
+
* When set, `toAnthropicTool()` in the agent loop prefers this over
|
|
62
|
+
* running `zodToJsonSchema(inputSchema)`. Normal in-process tools
|
|
63
|
+
* defined via `defineTool()` should leave this undefined.
|
|
64
|
+
*/
|
|
65
|
+
readonly anthropicSchemaOverride?: Record<string, unknown>;
|
|
66
|
+
/**
|
|
67
|
+
* Returns true if this tool is safe to run concurrently with other
|
|
68
|
+
* concurrency-safe tools. Read-only tools (Read, Glob, Grep, etc.)
|
|
69
|
+
* should return true; mutating tools (Write, Edit, Bash) should
|
|
70
|
+
* return false. Default: false (fail-closed).
|
|
71
|
+
*
|
|
72
|
+
* The agent loop uses this to batch consecutive safe tool calls
|
|
73
|
+
* into a single `Promise.all()` for parallel execution, while
|
|
74
|
+
* running unsafe tools serially.
|
|
75
|
+
*
|
|
76
|
+
* Input is typed as `unknown` (not `z.infer<TSchema>`) so that
|
|
77
|
+
* `Tool<ZodObject<...>>` remains assignable to `Tool<ZodTypeAny>`
|
|
78
|
+
* under `exactOptionalPropertyTypes`.
|
|
79
|
+
*/
|
|
80
|
+
isConcurrencySafe?: ((input: unknown) => boolean) | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Declares that this tool needs Node-only capabilities
|
|
83
|
+
* (`node:child_process`, the real filesystem, stdio-based MCP, etc.)
|
|
84
|
+
* When true AND the runtime can't support them, the engine replaces
|
|
85
|
+
* this tool with a capability stub (see `src/tools/capabilityStub.ts`)
|
|
86
|
+
* that returns a structured `isError` result instead of crashing.
|
|
87
|
+
*
|
|
88
|
+
* Leave undefined / false for tools that work anywhere (Read, Write,
|
|
89
|
+
* WebFetch, http-based MCP). Default: false.
|
|
90
|
+
*/
|
|
91
|
+
readonly requiresNode?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Internal marker set by `capabilityStub()` so the agent-loop's
|
|
94
|
+
* runner-handoff check (Plan 019 §4) can distinguish a stubbed tool
|
|
95
|
+
* from a real one without reflecting on the implementation.
|
|
96
|
+
* Do not set this manually.
|
|
97
|
+
*/
|
|
98
|
+
readonly isCapabilityStub?: boolean;
|
|
99
|
+
execute(input: z.infer<TSchema>, context: ToolContext): Promise<ToolResult>;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Identity helper that locks in the schema generic so callers don't have
|
|
103
|
+
* to write it. Use this whenever defining a tool — it costs nothing at
|
|
104
|
+
* runtime and gives you full inference inside `execute()`.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* const Echo = defineTool({
|
|
109
|
+
* name: 'Echo',
|
|
110
|
+
* description: 'Echoes its input back',
|
|
111
|
+
* inputSchema: z.object({ msg: z.string() }),
|
|
112
|
+
* execute: async ({ msg }) => ({ content: msg }),
|
|
113
|
+
* })
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
declare function defineTool<TSchema extends z.ZodTypeAny>(tool: Tool<TSchema>): Tool<TSchema>;
|
|
117
|
+
/**
|
|
118
|
+
* In-memory registry of tools by name. Used by the engine's tool runtime
|
|
119
|
+
* to dispatch tool calls. Re-registering the same name throws so typos
|
|
120
|
+
* surface immediately rather than silently shadowing.
|
|
121
|
+
*/
|
|
122
|
+
declare class ToolRegistry {
|
|
123
|
+
private readonly tools;
|
|
124
|
+
register(tool: Tool): void;
|
|
125
|
+
registerAll(tools: ReadonlyArray<Tool>): void;
|
|
126
|
+
unregister(name: string): void;
|
|
127
|
+
get(name: string): Tool | undefined;
|
|
128
|
+
has(name: string): boolean;
|
|
129
|
+
list(): Tool[];
|
|
130
|
+
count(): number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { type ToolResult as T, ToolRegistry as a, type Tool as b, type ToolContext as c, defineTool as d };
|