@codemation/agent-skills 0.1.10 → 0.2.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/CHANGELOG.md +32 -0
- package/package.json +1 -1
- package/skills/codemation-ai-agent-node/SKILL.md +128 -0
- package/skills/codemation-custom-node-development/SKILL.md +34 -27
- package/skills/codemation-custom-node-development/references/credential-aware-nodes.md +38 -0
- package/skills/codemation-custom-node-development/references/define-batch-node.md +38 -0
- package/skills/codemation-custom-node-development/references/define-node-per-item.md +61 -0
- package/skills/codemation-custom-node-development/references/node-patterns.md +141 -0
- package/skills/codemation-framework-concepts/SKILL.md +10 -1
- package/skills/codemation-mcp-capabilities/SKILL.md +85 -0
- package/skills/codemation-mcp-capabilities/references/agent-with-mcp.ts +44 -0
- package/skills/codemation-plugin-development/SKILL.md +43 -0
- package/skills/codemation-workflow-dsl/SKILL.md +206 -16
- package/skills/codemation-workflow-dsl/references/builder-patterns.md +47 -15
- package/skills/codemation-workflow-dsl/references/complete-example.md +263 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# @codemation/agent-skills
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8285ec0: Add `codemation-ai-agent-node` skill teaching the coding agent the `AIAgent` constructor signature, message shape, output contract (`{ output: string }`), and the four managed model IDs currently in the allowlist. Also covers BYOK (`OpenAIChatModelConfig`) and MCP tool attachment.
|
|
8
|
+
- 8285ec0: Add `codemation-mcp-capabilities` skill: documents the control-plane `GET /api/registry/capabilities` endpoint, MCP server credential kinds, and the workflow-author flow for discovering and binding MCP servers. Originally produced under Story 14 in a workspace-local install; relocated here so `pnpm codemation skills sync` picks it up.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- 8285ec0: docs(codemation-workflow-dsl): add "Verifying your workflow" section pointing agents to use `verify_workflow` MCP tool instead of `pnpm typecheck`.
|
|
13
|
+
- 8285ec0: Reorganize agent skills for faster coding-agent start-up (Story C).
|
|
14
|
+
- `codemation-framework-concepts` promoted to router skill: adds "Where to go next" section mapping tasks to skills, and updated frontmatter describing it as the index to read first.
|
|
15
|
+
- `codemation-custom-node-development` split into a slim TOC (53 lines) plus three new focused refs: `define-node-per-item.md`, `define-batch-node.md`, `credential-aware-nodes.md`. Binary/HTTP/MS-Graph patterns consolidated into the existing `node-patterns.md`.
|
|
16
|
+
|
|
17
|
+
- 8285ec0: Remove dead references to `describe_node` and `list_nodes` from workflow-dsl and ai-agent-node skills; replace with `find_examples` as the discovery surface.
|
|
18
|
+
- 8285ec0: Sprint 11 Story D: deepen `find_examples` as the canonical discovery surface in skills.
|
|
19
|
+
- Expand `codemation-workflow-dsl` "Discovering nodes and patterns" section with why examples are canonical, when to call first, two query patterns, zero-hit response (ask user or adapt with confirmation), and anti-pattern (don't grep node_modules).
|
|
20
|
+
- Strengthen `codemation-ai-agent-node` call-to-action with concrete `find_examples` query suggestions for AIAgent patterns.
|
|
21
|
+
|
|
22
|
+
- 8285ec0: docs(codemation-workflow-dsl): note that search results include install-state fields (`installed`, `requiresInstall`) and how agents should use them to plan `install_package` calls.
|
|
23
|
+
- 8285ec0: Sprint 12 Story C: add self-solving fallback chain to `codemation-workflow-dsl` skill.
|
|
24
|
+
- Add "When no example matches — the self-solving fallback chain" section after "Discovering nodes and patterns".
|
|
25
|
+
- Four-tier chain: retry with intent variations → defineRestNode (HTTP APIs) → HttpRequest (inline one-off) → defineNode (non-HTTP custom logic).
|
|
26
|
+
- Explicit "What NOT to do" and "Surfacing what you did" sub-sections.
|
|
27
|
+
- Agent never asks the non-technical user for fallback choices; picks per the chain and reports what it used.
|
|
28
|
+
|
|
29
|
+
- 8285ec0: Sprint 14 coverage: tests for WhenBuilder DSL helper, InMemoryWorkflowExecutionRepository retention paths, DevTrackedProcessTreeKiller edge cases, ConsumerCliTsconfigPreparation resolution, ListenPortConflictDescriber ss fallback, RedisRunEventBus publish/subscribe/teardown, CodemationChatModelFactory HMAC signing, registerCoreNodes smoke, single-react-component-per-file rule branches, and CodemationAgentSkillsCli error/help paths. No production code changes.
|
|
30
|
+
- 8285ec0: docs(skills): add complete workflow example to workflow-dsl skill
|
|
31
|
+
- 8285ec0: Fix workflow-dsl skill docs: `workflow("id")` is manual-trigger only. Cron, webhook, and other non-manual triggers must use `createWorkflowBuilder({id, name}).trigger(new XxxTrigger(...))` and chain with `.then(new SomeNodeConfig(...))` — the fluent `.map`/`.if`/`.agent`/`.node` sugar is only available via `manualTrigger(...)`.
|
|
32
|
+
|
|
33
|
+
Affected: `codemation-workflow-dsl/SKILL.md`, `codemation-workflow-dsl/references/builder-patterns.md`, `codemation-workflow-dsl/references/complete-example.md`, `codemation-mcp-capabilities/references/agent-with-mcp.ts`.
|
|
34
|
+
|
|
3
35
|
## 0.1.10
|
|
4
36
|
|
|
5
37
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: codemation-ai-agent-node
|
|
3
|
+
description: AIAgent constructor, message shape, output contract, and chat-model configs (managed and BYOK). Read before writing any workflow that uses AIAgent.
|
|
4
|
+
compatibility: Codemation core-nodes. Requires @codemation/core-nodes import.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Codemation AI Agent Node
|
|
8
|
+
|
|
9
|
+
> **Start here: call `find_examples` before reading further.**
|
|
10
|
+
>
|
|
11
|
+
> - `find_examples({ query: "AIAgent" })` — basic usage and constructor patterns
|
|
12
|
+
> - `find_examples({ query: "AIAgent multi-step" })` — chained pipeline patterns
|
|
13
|
+
> - `find_examples({ query: "AIAgent tools" })` — agent with callable tools
|
|
14
|
+
> - `find_examples({ query: "AIAgent gmail classify" })` — domain-specific examples
|
|
15
|
+
>
|
|
16
|
+
> The sections below are a quick orientation for when you need the exact constructor or output shape.
|
|
17
|
+
|
|
18
|
+
## Use this skill when
|
|
19
|
+
|
|
20
|
+
Writing a workflow that uses `AIAgent` — classification, extraction, summarisation, drafting, decision, or any step that calls an LLM.
|
|
21
|
+
Use `codemation-workflow-dsl` for the surrounding workflow structure.
|
|
22
|
+
Use `codemation-mcp-capabilities` when the agent needs MCP servers.
|
|
23
|
+
|
|
24
|
+
## When to use `AIAgent` vs other approaches
|
|
25
|
+
|
|
26
|
+
Use `AIAgent` when an item needs an LLM call with a fixed or per-item prompt and optional tool use.
|
|
27
|
+
Use a plain `Callback` instead when the logic is deterministic code (no LLM needed).
|
|
28
|
+
Use the `.agent(...)` fluent helper on a manual-trigger workflow only if you need the full fluent chain sugar — under the hood it also produces an `AIAgent`.
|
|
29
|
+
|
|
30
|
+
## Constructor
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { AIAgent } from "@codemation/core-nodes";
|
|
34
|
+
|
|
35
|
+
new AIAgent({
|
|
36
|
+
name: string, // display name and default node id slug
|
|
37
|
+
messages: AgentMessageConfig, // see below
|
|
38
|
+
chatModel: ChatModelConfig, // see Managed and BYOK sections below
|
|
39
|
+
tools?: ReadonlyArray<ToolConfig>, // optional callable tools
|
|
40
|
+
id?: string, // stable node id (set explicitly if node has credential bindings)
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## `messages` shape
|
|
45
|
+
|
|
46
|
+
`messages` is an ordered array of `{ role, content }` objects.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
messages: [
|
|
50
|
+
{ role: "system", content: "You are a helpful assistant that classifies emails." },
|
|
51
|
+
{ role: "user", content: (args) => `Classify this email:\n\n${args.item.json.body}` },
|
|
52
|
+
];
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- `role` is `"system"` | `"user"` (use `"assistant"` only for few-shot examples — rare).
|
|
56
|
+
- `content` can be a plain string or a function `(args: { item, itemIndex, items, ctx }) => string`.
|
|
57
|
+
- Put the detailed instructions in the `system` message and the per-item data in the `user` message.
|
|
58
|
+
|
|
59
|
+
## Output shape
|
|
60
|
+
|
|
61
|
+
`AIAgent` emits `{ output: string }` on its single port `main`.
|
|
62
|
+
|
|
63
|
+
The next node sees `item.json.output` as the agent's text response.
|
|
64
|
+
Type your downstream `Callback` accordingly:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
.then(new Callback<{ output: string }>("Handle result", (item) => {
|
|
68
|
+
const reply = item.json.output;
|
|
69
|
+
// ...
|
|
70
|
+
}))
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
If you set `outputSchema` (a Zod schema), the agent validates and parses the output into a structured object. Without `outputSchema`, `item.json.output` is always a plain string.
|
|
74
|
+
|
|
75
|
+
## Managed model (no credentials needed)
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { AIAgent, CodemationChatModelConfig } from "@codemation/core-nodes";
|
|
79
|
+
|
|
80
|
+
new AIAgent({
|
|
81
|
+
name: "Classify email",
|
|
82
|
+
messages: [
|
|
83
|
+
{ role: "system", content: "Classify the email as spam or not-spam." },
|
|
84
|
+
{ role: "user", content: (args) => args.item.json.body as string },
|
|
85
|
+
],
|
|
86
|
+
chatModel: new CodemationChatModelConfig(
|
|
87
|
+
"Claude Haiku", // display label
|
|
88
|
+
"anthropic/claude-haiku-4-5-20251001", // managed model id
|
|
89
|
+
),
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Currently allowlisted managed models
|
|
94
|
+
|
|
95
|
+
| Model id | Notes |
|
|
96
|
+
| ------------------------------------- | -------------------- |
|
|
97
|
+
| `anthropic/claude-haiku-4-5-20251001` | Fastest and cheapest |
|
|
98
|
+
| `anthropic/claude-sonnet-4-6` | Balanced |
|
|
99
|
+
| `anthropic/claude-opus-4-5-20251101` | High capability |
|
|
100
|
+
| `anthropic/claude-opus-4-6` | Latest flagship |
|
|
101
|
+
|
|
102
|
+
Discover live: `GET <CONTROL_PLANE_URL>/api/llm/managed-models`
|
|
103
|
+
|
|
104
|
+
## BYOK model (user supplies their own key)
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { AIAgent, OpenAIChatModelConfig } from "@codemation/core-nodes";
|
|
108
|
+
|
|
109
|
+
new AIAgent({
|
|
110
|
+
name: "Summarise",
|
|
111
|
+
id: "summarise-agent", // stable id — required when node has a credential binding
|
|
112
|
+
messages: [
|
|
113
|
+
{ role: "system", content: "Summarise the following text in one paragraph." },
|
|
114
|
+
{ role: "user", content: (args) => args.item.json.text as string },
|
|
115
|
+
],
|
|
116
|
+
chatModel: new OpenAIChatModelConfig(
|
|
117
|
+
"OpenAI GPT-4o", // display label
|
|
118
|
+
"gpt-4o", // OpenAI model id
|
|
119
|
+
"openai", // credential slot key — matches the slot used in getCredentialRequirements
|
|
120
|
+
),
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`OpenAIChatModelConfig` requires the user to connect an `openai.apiKey` credential. The concierge handles credential acquisition — the coding agent must not invent credentials.
|
|
125
|
+
|
|
126
|
+
## MCP servers
|
|
127
|
+
|
|
128
|
+
If you need tools / MCP servers on the agent, see the `codemation-mcp-capabilities` skill.
|
|
@@ -12,35 +12,42 @@ Use this skill for reusable custom node work, whether the node lives inside an a
|
|
|
12
12
|
|
|
13
13
|
Do not use this skill for pure workflow chaining questions unless the node implementation itself is changing.
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Per-item vs batch
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
2. Implement **`execute(args, context)`** — one mapped **input** in, one output payload per item (activations are still batch-shaped; the engine iterates items for you).
|
|
19
|
-
3. Give the node a stable key and a clear title.
|
|
20
|
-
4. Optionally set **`icon`** on the `defineNode` definition so the workflow canvas shows a proper glyph (same string contract as `NodeConfigBase.icon`).
|
|
21
|
-
5. Use **`defineBatchNode(...)`** with **`run(items, context)`** only when the node must process the **entire batch** at once (legacy batch semantics).
|
|
22
|
-
6. Promote callback-heavy logic into a node when the graph or tests need a stronger boundary.
|
|
17
|
+
**`defineNode(...)` (per-item)** — the engine calls `execute(args, context)` once per item. This is the right default for the vast majority of nodes: straightforward logic, credential slots, input schema, optional fan-out.
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
1. Prefer helper-based nodes first.
|
|
27
|
-
2. Keep nodes deterministic and focused.
|
|
28
|
-
3. Request credentials through named slots instead of hard-coded secrets.
|
|
29
|
-
4. Put **static** options (credentials, retry policy, labels) on **config**; put **per-item** behavior in **inputs** / wire JSON and optional **`itemExpr`** on config fields (consistent with built-in nodes).
|
|
30
|
-
5. **Emit files with `ctx.binary`, not base64 in `json`:** use **`attach`** + **`withAttachment`** on **`args.ctx.binary`** (`defineNode`) or **`ctx.binary`** (class nodes). Base64 in **`item.json`** bloats persisted run JSON in the database; binaries use **storage + references** only. See `references/node-patterns.md` and repo docs **Concepts → Execution model** / **Custom nodes**.
|
|
31
|
-
6. Drop to class-based node APIs only when you need constructor-injected collaborators, decorators, or deeper runtime metadata.
|
|
32
|
-
|
|
33
|
-
## Testing with `WorkflowTestKit`
|
|
34
|
-
|
|
35
|
-
For engine-backed tests without the host, use **`WorkflowTestKit`** from **`@codemation/core/testing`**: **`registerDefinedNodes([...])`**, then **`runNode`** or **`run`**. See the plugin development doc and `@codemation/core` tests for examples.
|
|
19
|
+
**`defineBatchNode(...)` (batch)** — the engine calls `run(items, context)` with the full activation batch. Use only when the node genuinely needs to see all items at once (aggregation, bulk API calls, cross-item correlation).
|
|
36
20
|
|
|
37
|
-
|
|
21
|
+
When in doubt, start with `defineNode`.
|
|
38
22
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
Custom **per-item nodes** can also read **`ctx.testContext?.{testSuiteRunId, testCaseIndex}`** to branch on test mode without an `IsTestRun` upstream — useful for synthetic outputs or skipping irreversible side effects when running tests.
|
|
42
|
-
|
|
43
|
-
## Read next when needed
|
|
23
|
+
## Node rules
|
|
44
24
|
|
|
45
|
-
|
|
46
|
-
|
|
25
|
+
1. Keep nodes deterministic and focused.
|
|
26
|
+
2. Request credentials through named slots — never hard-code secrets.
|
|
27
|
+
3. Put **static** options (credentials, retry policy, labels) on **config**; put **per-item** behavior in **inputs** / wire JSON and optional `itemExpr` on config fields.
|
|
28
|
+
4. **Emit files with `ctx.binary`, not base64 in `json`** — base64 in `item.json` bloats persisted run data. See `references/node-patterns.md`.
|
|
29
|
+
5. Drop to class-based node APIs only when you need constructor-injected collaborators, decorators, or deeper runtime metadata.
|
|
30
|
+
|
|
31
|
+
## Minimal `defineNode` example
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { defineNode } from "@codemation/core";
|
|
35
|
+
import { z } from "zod";
|
|
36
|
+
|
|
37
|
+
export const uppercaseNode = defineNode({
|
|
38
|
+
key: "example.uppercase",
|
|
39
|
+
title: "Uppercase field",
|
|
40
|
+
icon: "lucide:languages",
|
|
41
|
+
inputSchema: z.object({ field: z.string() }),
|
|
42
|
+
async execute({ input }) {
|
|
43
|
+
return { ...input, field: input.field.toUpperCase() };
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Read next
|
|
49
|
+
|
|
50
|
+
- `references/define-node-per-item.md` — full `defineNode(...)` contract, `inputSchema`, `itemExpr`, fan-out, assertion nodes, and `WorkflowTestKit` usage. Load this when writing or debugging a per-item node.
|
|
51
|
+
- `references/define-batch-node.md` — `defineBatchNode(...)` contract and when to choose batch over per-item. Load this when the node must see the entire batch at once.
|
|
52
|
+
- `references/credential-aware-nodes.md` — credential slots, typed sessions, and how to test credential-aware nodes. Load this when your node needs a credential.
|
|
53
|
+
- `references/node-patterns.md` — binary payloads (`ctx.binary`, `attach`, `withAttachment`), fan-out return shapes, polling-trigger binary patterns, MS Graph attachment download, and HTTP binary round-trips. Load this when working with file data or HTTP binaries.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Credential-Aware Nodes
|
|
2
|
+
|
|
3
|
+
Load this when your node needs a typed credential (OAuth token, API key, or any `defineCredential(...)` type) injected at runtime.
|
|
4
|
+
|
|
5
|
+
## Core rule
|
|
6
|
+
|
|
7
|
+
Request credentials through **named slots** on the node config instead of hard-coding secrets. The framework resolves the slot to a live typed session at execution time.
|
|
8
|
+
|
|
9
|
+
## Adding a credential slot to `defineNode`
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { defineNode } from "@codemation/core";
|
|
13
|
+
import { myApiCredentialType } from "./myApiCredential.js";
|
|
14
|
+
|
|
15
|
+
export const callApiNode = defineNode({
|
|
16
|
+
key: "example.call-api",
|
|
17
|
+
title: "Call My API",
|
|
18
|
+
credentials: {
|
|
19
|
+
api: myApiCredentialType, // slot name → credential type
|
|
20
|
+
},
|
|
21
|
+
async execute({ input }, { credentials }) {
|
|
22
|
+
const session = await credentials.api.getSession();
|
|
23
|
+
// session is typed by myApiCredentialType.sessionSchema
|
|
24
|
+
const response = await fetch("https://api.example.com/data", {
|
|
25
|
+
headers: { Authorization: `Bearer ${session.accessToken}` },
|
|
26
|
+
});
|
|
27
|
+
return response.json();
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Typed sessions
|
|
33
|
+
|
|
34
|
+
`credentials.<slot>.getSession()` returns the shape declared in the credential type's `sessionSchema`. The framework handles refresh, storage, and error propagation — your node only consumes the session.
|
|
35
|
+
|
|
36
|
+
## Testing credential-aware nodes
|
|
37
|
+
|
|
38
|
+
Supply a mock credential in `WorkflowTestKit` rather than live credentials. See `codemation-credential-development` for the full `defineCredential(...)` story, typed sessions, and credential testing patterns.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Define Batch Node
|
|
2
|
+
|
|
3
|
+
Load this when you need to author a `defineBatchNode(...)` node that processes all items in one call.
|
|
4
|
+
|
|
5
|
+
## When to use `defineBatchNode` instead of `defineNode`
|
|
6
|
+
|
|
7
|
+
- The node must see the **entire activation batch** at once (e.g. an aggregation, a bulk API call, or a node that correlates items against each other).
|
|
8
|
+
- Legacy batch semantics are required by the calling workflow.
|
|
9
|
+
- You need the same contract as built-in batch-shaped nodes such as `Aggregate`.
|
|
10
|
+
|
|
11
|
+
For the common case (one-item-at-a-time logic), prefer `defineNode` — the engine handles iteration for you.
|
|
12
|
+
|
|
13
|
+
## Minimal skeleton
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { defineBatchNode } from "@codemation/core";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
|
|
19
|
+
export const sumNode = defineBatchNode({
|
|
20
|
+
key: "example.sum",
|
|
21
|
+
title: "Sum numeric field",
|
|
22
|
+
inputSchema: z.object({ value: z.number() }),
|
|
23
|
+
async run(items, { config }) {
|
|
24
|
+
const total = items.reduce((acc, item) => acc + (item.json as { value: number }).value, 0);
|
|
25
|
+
return [{ json: { total } }];
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Contract
|
|
31
|
+
|
|
32
|
+
- `run(items, context)` receives the **full array** of activation items.
|
|
33
|
+
- Return an array of output items (same length as input is not required — you can fan-in to one, or fan-out to many).
|
|
34
|
+
- The context object exposes `config`, `credentials`, and `execution` (same as `defineNode`).
|
|
35
|
+
|
|
36
|
+
## Advanced fallback
|
|
37
|
+
|
|
38
|
+
Reach for class-based node APIs when constructor-injected collaborators are required, plugin packaging needs the lower-level runtime contract, or decorators/persisted metadata need tighter control.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Define Node (Per-Item)
|
|
2
|
+
|
|
3
|
+
Load this when you need to author a `defineNode(...)` node that processes one item at a time.
|
|
4
|
+
|
|
5
|
+
## When to use `defineNode`
|
|
6
|
+
|
|
7
|
+
- Node logic is straightforward.
|
|
8
|
+
- The node belongs to one app or plugin package.
|
|
9
|
+
- Helper-based credential slots are sufficient.
|
|
10
|
+
- You do not need to inspect the entire batch in one call.
|
|
11
|
+
|
|
12
|
+
## Minimal skeleton
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { defineNode } from "@codemation/core";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
|
|
18
|
+
export const uppercaseNode = defineNode({
|
|
19
|
+
key: "example.uppercase",
|
|
20
|
+
title: "Uppercase field",
|
|
21
|
+
icon: "lucide:languages", // optional — Lucide, builtin:, si:, or image URL
|
|
22
|
+
inputSchema: z.object({ field: z.string() }),
|
|
23
|
+
async execute({ input, item }, { config }) {
|
|
24
|
+
return { ...input, [config.field]: String(input.field).toUpperCase() };
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Contract
|
|
30
|
+
|
|
31
|
+
- `execute(args, context)` is called **once per item** by the engine.
|
|
32
|
+
- Return a plain JSON object → one output item on `main`.
|
|
33
|
+
- Return a top-level array → **fan-out** (one item per element).
|
|
34
|
+
- Return `emitPorts({ portName: [...] })` for multi-port routing.
|
|
35
|
+
- Return an item-shaped `{ json, binary?, meta?, paired? }` when you need explicit binary/meta control.
|
|
36
|
+
|
|
37
|
+
## Config fields with `itemExpr`
|
|
38
|
+
|
|
39
|
+
Place **static** options (credentials, retry policy, labels) on `config`; place **per-item** values in `inputs` using `itemExpr` on config fields — consistent with built-in nodes.
|
|
40
|
+
|
|
41
|
+
## Input schema and `inputSchema`
|
|
42
|
+
|
|
43
|
+
Supply `inputSchema` (Zod) to get typed `input` in `execute` and to drive the canvas form. The engine validates items against it before calling `execute`.
|
|
44
|
+
|
|
45
|
+
## Testing with `WorkflowTestKit`
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { createEngineTestKit, registerDefinedNodes } from "@codemation/core/testing";
|
|
49
|
+
|
|
50
|
+
const kit = createEngineTestKit();
|
|
51
|
+
registerDefinedNodes([uppercaseNode]);
|
|
52
|
+
const result = await kit.runNode(uppercaseNode, { json: { field: "hello" } });
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Use `WorkflowTestKit` from `@codemation/core/testing` for engine-backed tests without the host.
|
|
56
|
+
|
|
57
|
+
## Custom assertion nodes
|
|
58
|
+
|
|
59
|
+
Set `emitsAssertions: true` on the node config to record results into `TestSuiteRun` infrastructure. The host's `TestSuiteRunTracker` listens for `nodeCompleted` events on runs with `ctx.testContext` set and persists each emitted item (matching `AssertionResult`) as a `TestAssertion` row.
|
|
60
|
+
|
|
61
|
+
Per-item nodes can also read `ctx.testContext?.{testSuiteRunId, testCaseIndex}` to branch on test mode — useful for synthetic outputs or skipping irreversible side effects.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Node Patterns
|
|
2
2
|
|
|
3
|
+
Load this when working with file data, binary payloads, HTTP binaries, MS Graph attachments, or when you need reference on fan-out return shapes and polling-trigger binary patterns.
|
|
4
|
+
|
|
3
5
|
## Start here
|
|
4
6
|
|
|
5
7
|
Use `defineNode(...)` when:
|
|
@@ -78,3 +80,142 @@ Reach for class-based node APIs when:
|
|
|
78
80
|
1. In `runCycle` (the polling step), fetch only the **metadata** (id, name, contentType, size). The result is persisted into the trigger's setup state and into emitted item JSON, so it must stay small.
|
|
79
81
|
2. In `execute(items, ctx)`, when the cfg opts into downloads, fetch each blob's bytes from the source API and register them via `ctx.binary.attach(...)`. Then return items via `ctx.binary.withAttachment(item, slot, stored)`.
|
|
80
82
|
- **Do not** request the full payload in the polling fetch (e.g. Microsoft Graph `$expand=attachments` returns base64 `contentBytes` inline; use `$expand=attachments($select=id,name,contentType,size)` to keep the response light). Large polling responses bloat the run state on every cycle, even when no item is emitted.
|
|
83
|
+
|
|
84
|
+
## Binary payloads in sub-workflow chains
|
|
85
|
+
|
|
86
|
+
Binary slots attached inside a node survive SubWorkflow boundaries with no extra work. The shared `BinaryStorage` DI singleton means `ctx.binary.openReadStream` works regardless of which run originally stored the bytes.
|
|
87
|
+
|
|
88
|
+
### Pattern: attach in a node, read in the parent after SubWorkflow
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
// Child node — attaches a slot and returns the modified item.
|
|
92
|
+
export const parseAndStoreNode = defineNode({
|
|
93
|
+
key: "example.parse-store",
|
|
94
|
+
title: "Parse and Store",
|
|
95
|
+
inputSchema: z.object({ filename: z.string() }),
|
|
96
|
+
async execute({ input, item }, { binary }) {
|
|
97
|
+
const bytes = Buffer.from("...parsed content...");
|
|
98
|
+
const att = await binary.attach({
|
|
99
|
+
name: "parsed",
|
|
100
|
+
body: bytes,
|
|
101
|
+
mimeType: "text/plain",
|
|
102
|
+
filename: `${input.filename}.txt`,
|
|
103
|
+
});
|
|
104
|
+
return binary.withAttachment(item, "parsed", att);
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
After `SubWorkflowNode` returns, the parent's continuation nodes see `item.binary["parsed"]` and can call `ctx.binary.openReadStream(item.binary["parsed"])` to read the bytes.
|
|
110
|
+
|
|
111
|
+
### Testing binary across SubWorkflow with `WorkflowTestKit`
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { DefaultExecutionContextFactory, InMemoryBinaryStorage } from "@codemation/core";
|
|
115
|
+
import { createEngineTestKit } from "@codemation/core/testing";
|
|
116
|
+
import { ItemHarnessNodeConfig } from "@codemation/core/testing";
|
|
117
|
+
|
|
118
|
+
const storage = new InMemoryBinaryStorage();
|
|
119
|
+
const kit = createEngineTestKit({
|
|
120
|
+
executionContextFactory: new DefaultExecutionContextFactory(storage),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Use ItemHarnessNodeConfig (NOT CallbackNodeConfig) for nodes that must modify items:
|
|
124
|
+
const attachNode = new ItemHarnessNodeConfig(
|
|
125
|
+
"Attach",
|
|
126
|
+
z.unknown(),
|
|
127
|
+
async ({ item, ctx }) => {
|
|
128
|
+
const att = await ctx.binary.attach({
|
|
129
|
+
name: "doc",
|
|
130
|
+
body: Buffer.from("content"),
|
|
131
|
+
mimeType: "application/pdf",
|
|
132
|
+
filename: "doc.pdf",
|
|
133
|
+
});
|
|
134
|
+
return ctx.binary.withAttachment(item as Item, "doc", att);
|
|
135
|
+
},
|
|
136
|
+
{ id: "attach" },
|
|
137
|
+
);
|
|
138
|
+
// CallbackNodeConfig is fine for assertion-only (observe) nodes — it echoes input unchanged.
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Important: `CallbackNodeConfig` discards its callback return value and always echoes input items. Never use it for nodes that must attach binary or transform items.
|
|
142
|
+
|
|
143
|
+
## MS Graph: selective attachment download
|
|
144
|
+
|
|
145
|
+
Use `OutlookAttachmentDownload` from `@codemation/core-nodes-msgraph` when you have already obtained attachment metadata (filename, contentType, id) and want to download only specific attachments.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { onNewMsGraphMailTrigger, outlookAttachmentDownloadNode } from "@codemation/core-nodes-msgraph";
|
|
149
|
+
|
|
150
|
+
workflow("wf.download-resumes")
|
|
151
|
+
.trigger(onNewMsGraphMailTrigger, { mailbox: "me", folderId: "inbox" })
|
|
152
|
+
.then(
|
|
153
|
+
outlookAttachmentDownloadNode.create(
|
|
154
|
+
{
|
|
155
|
+
messageId: "", // falls back to item.json when empty
|
|
156
|
+
attachmentId: "", // falls back to item.json when empty
|
|
157
|
+
binarySlot: "resume",
|
|
158
|
+
sizeCapBytes: 10 * 1024 * 1024,
|
|
159
|
+
},
|
|
160
|
+
"DownloadResume",
|
|
161
|
+
),
|
|
162
|
+
)
|
|
163
|
+
.build();
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Key constraints:
|
|
167
|
+
|
|
168
|
+
- Only `#microsoft.graph.fileAttachment` is supported — `itemAttachment` / `referenceAttachment` throw immediately.
|
|
169
|
+
- Set `keepBinaries: true` on any downstream node that needs to pass the binary slot forward.
|
|
170
|
+
- The credential is `msGraphMailOAuthCredentialType`; `Mail.Read` scope is sufficient.
|
|
171
|
+
|
|
172
|
+
## HTTP + binary: download to a slot, then upload from a slot
|
|
173
|
+
|
|
174
|
+
`HttpRequest` (from `@codemation/core-nodes`) natively handles binary response and request bodies.
|
|
175
|
+
|
|
176
|
+
### Download a file to a binary slot
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { HttpRequest } from "@codemation/core-nodes";
|
|
180
|
+
import { workflow } from "@codemation/host";
|
|
181
|
+
|
|
182
|
+
export default workflow("wf.download-pdf")
|
|
183
|
+
.manualTrigger<{ url: string }>("Start", { url: "" })
|
|
184
|
+
.then(
|
|
185
|
+
new HttpRequest("DownloadResume", {
|
|
186
|
+
responseFormat: "binary",
|
|
187
|
+
responseBinarySlot: "resume", // default is "response"
|
|
188
|
+
responseSizeCapBytes: 10 * 1024 * 1024, // 10 MiB cap (default 100 MiB)
|
|
189
|
+
}),
|
|
190
|
+
)
|
|
191
|
+
.build();
|
|
192
|
+
// item.json gets: { status, headers, binarySlot, contentType, size, filename? }
|
|
193
|
+
// item.binary["resume"] holds the BinaryAttachment reference — never base64.
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Upload binary bytes from a slot
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
new HttpRequest("UploadResume", {
|
|
200
|
+
method: "POST",
|
|
201
|
+
url: "https://api.example.com/files",
|
|
202
|
+
body: { kind: "binary", slot: "resume" },
|
|
203
|
+
// Content-Type defaults to the attachment's mimeType.
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Download then upload (full round-trip)
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
export default workflow("wf.mirror-pdf")
|
|
211
|
+
.manualTrigger<{ sourceUrl: string; targetUrl: string }>("Start", { sourceUrl: "", targetUrl: "" })
|
|
212
|
+
.then(new HttpRequest("Download", { urlField: "sourceUrl", responseFormat: "binary", responseBinarySlot: "file" }))
|
|
213
|
+
.then(new HttpRequest("Upload", { urlField: "targetUrl", method: "PUT", body: { kind: "binary", slot: "file" } }))
|
|
214
|
+
.build();
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Key rules:
|
|
218
|
+
|
|
219
|
+
- Never put bytes or base64 in `item.json` — always use `ctx.binary`.
|
|
220
|
+
- `responseSizeCapBytes` is checked against `Content-Length` before reading the body; set it for untrusted sources.
|
|
221
|
+
- Use `keepBinaries: true` on downstream nodes that must forward the slot.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: codemation-framework-concepts
|
|
3
|
-
description: Explains Codemation package boundaries, runtime concepts, observability shape, and the normal consumer mental model. Use when the user asks where code belongs across `@codemation/core`, `@codemation/host`, `@codemation/next-host`, `@codemation/cli`, workflows, plugins, credentials, activation, telemetry, or runtime modes.
|
|
3
|
+
description: Explains Codemation package boundaries, runtime concepts, observability shape, and the normal consumer mental model. Use when the user asks where code belongs across `@codemation/core`, `@codemation/host`, `@codemation/next-host`, `@codemation/cli`, workflows, plugins, credentials, activation, telemetry, or runtime modes. Read this first when starting any Codemation task — it points at the right skill for the work.
|
|
4
4
|
compatibility: Designed for Codemation apps, plugins, and framework contributors.
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -39,6 +39,15 @@ Do not use this skill as a substitute for detailed CLI, workflow DSL, or plugin
|
|
|
39
39
|
3. Keep workflow code stable while the runtime shape grows around it.
|
|
40
40
|
4. Treat telemetry as part of the runtime contract, not as ad-hoc node-local logging.
|
|
41
41
|
|
|
42
|
+
## Where to go next
|
|
43
|
+
|
|
44
|
+
- Authoring workflows → `codemation-workflow-dsl`
|
|
45
|
+
- Building a reusable node → `codemation-custom-node-development`
|
|
46
|
+
- Building a credential type → `codemation-credential-development`
|
|
47
|
+
- Packaging as a plugin → `codemation-plugin-development`
|
|
48
|
+
- Calling an MCP server from a workflow → `codemation-mcp-capabilities`
|
|
49
|
+
- CLI commands / dev loop → `codemation-cli`
|
|
50
|
+
|
|
42
51
|
## Read next when needed
|
|
43
52
|
|
|
44
53
|
- Read `references/architecture-map.md` for package ownership and runtime-mode guidance.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: codemation-mcp-capabilities
|
|
3
|
+
description: Discover MCP servers registered on the Codemation control plane. Use before authoring agent workflows that reference mcpServers to find available server ids and their credential requirements.
|
|
4
|
+
compatibility: Requires an installation paired with a connected control plane (Sprint 2+).
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Codemation MCP Capabilities
|
|
8
|
+
|
|
9
|
+
## Use this skill when
|
|
10
|
+
|
|
11
|
+
Use this skill before writing `agent({ mcpServers: ["..."] })` to discover what server ids are
|
|
12
|
+
available and what credential types they require. Without it, you'd have to guess server ids or
|
|
13
|
+
ask the user.
|
|
14
|
+
|
|
15
|
+
## How to search
|
|
16
|
+
|
|
17
|
+
Call `GET /api/registry/capabilities?query=<search term>` on the control-plane API.
|
|
18
|
+
The endpoint is session-authenticated (the control-plane session cookie is forwarded automatically
|
|
19
|
+
when called from within the workspace's paired context).
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
GET /api/registry/capabilities?query=gmail
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Response shape (array of capability objects):
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
[
|
|
29
|
+
{
|
|
30
|
+
"kind": "mcp-server",
|
|
31
|
+
"id": "gmail",
|
|
32
|
+
"displayName": "Gmail",
|
|
33
|
+
"description": "Read, send, and manage Gmail messages and labels.",
|
|
34
|
+
"acceptedCredentialTypes": ["oauth.google.gmail"]
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
An empty query string returns all registered servers.
|
|
40
|
+
|
|
41
|
+
## Response fields
|
|
42
|
+
|
|
43
|
+
| Field | Type | Notes |
|
|
44
|
+
| ------------------------ | -------- | --------------------------------------------------------------------- |
|
|
45
|
+
| `kind` | string | Always `"mcp-server"` for now. Future: `"node"`, `"credential-type"` |
|
|
46
|
+
| `id` | string | Stable slug — add this string to the agent's mcpServers array |
|
|
47
|
+
| `displayName` | string | Human-readable name for UI or explanations |
|
|
48
|
+
| `description` | string | What the server does |
|
|
49
|
+
| `acceptedCredentialTypes`| string[] | Credential type ids accepted by this server (empty = no credential) |
|
|
50
|
+
|
|
51
|
+
## Credential types
|
|
52
|
+
|
|
53
|
+
- **`"oauth.google.gmail"`** — user must connect a Google account credential instance via the
|
|
54
|
+
credential dialog before the workflow runs. The same credential instance can be shared between
|
|
55
|
+
a `GmailTrigger` node and the Gmail MCP server.
|
|
56
|
+
- **`"bearer_token"`** etc. — user configures a static credential via the credential dialog.
|
|
57
|
+
- **empty array** — no credential required. The server is usable immediately.
|
|
58
|
+
|
|
59
|
+
## Using results in workflow config
|
|
60
|
+
|
|
61
|
+
The `id` field from the response is added to the agent's `mcpServers` array. Each entry
|
|
62
|
+
surfaces a credential slot on the materialized MCP connection node (same shape as
|
|
63
|
+
ChatModel and Tool connection nodes); the user picks a specific credential instance via
|
|
64
|
+
the canvas credential dropdown — same flow as a trigger credential. A user may have
|
|
65
|
+
multiple instances of the same type (personal vs work Gmail); the dropdown surfaces all
|
|
66
|
+
matching instances.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
new AIAgent({
|
|
70
|
+
name: "Gmail reader",
|
|
71
|
+
mcpServers: ["gmail"],
|
|
72
|
+
// ...
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Bind the credential instance via the UI before activation; there is no inline credential
|
|
77
|
+
field on the workflow definition.
|
|
78
|
+
|
|
79
|
+
## Example flow
|
|
80
|
+
|
|
81
|
+
1. User asks: "Build a workflow that reads Gmail and summarises unread messages."
|
|
82
|
+
2. Call `GET /api/registry/capabilities?query=gmail` → find `id: "gmail"`, `acceptedCredentialTypes: ["oauth.google.gmail"]`.
|
|
83
|
+
3. Report back: "Gmail MCP is available. The user will need to bind a `oauth.google.gmail` credential instance."
|
|
84
|
+
4. In the workflow, use `mcpServers: ["gmail"]`.
|
|
85
|
+
5. The user binds their credential instance via the canvas credential dropdown before activating.
|