@graph-compose/client 1.0.0 → 1.0.3
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 +949 -476
- package/dist/core/adk-helpers.d.ts +94 -0
- package/dist/core/adk-helpers.d.ts.map +1 -0
- package/dist/core/adk-helpers.js +134 -0
- package/dist/core/adk-helpers.js.map +1 -0
- package/dist/core/adk-node-builder.d.ts +128 -0
- package/dist/core/adk-node-builder.d.ts.map +1 -0
- package/dist/core/adk-node-builder.js +175 -0
- package/dist/core/adk-node-builder.js.map +1 -0
- package/dist/core/adk-workflow-builder.d.ts +74 -0
- package/dist/core/adk-workflow-builder.d.ts.map +1 -0
- package/dist/core/adk-workflow-builder.js +138 -0
- package/dist/core/adk-workflow-builder.js.map +1 -0
- package/dist/core/base-builder.d.ts +80 -0
- package/dist/core/base-builder.d.ts.map +1 -0
- package/dist/core/base-builder.js +63 -0
- package/dist/core/base-builder.js.map +1 -0
- package/dist/core/builder.d.ts +35 -67
- package/dist/core/builder.d.ts.map +1 -1
- package/dist/core/builder.js +45 -144
- package/dist/core/builder.js.map +1 -1
- package/dist/core/node-builder.d.ts +15 -59
- package/dist/core/node-builder.d.ts.map +1 -1
- package/dist/core/node-builder.js +36 -106
- package/dist/core/node-builder.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +50 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/validation/adk-validation.d.ts +10 -0
- package/dist/validation/adk-validation.d.ts.map +1 -0
- package/dist/validation/adk-validation.js +362 -0
- package/dist/validation/adk-validation.js.map +1 -0
- package/dist/validation/index.d.ts +34 -9
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +40 -131
- package/dist/validation/index.js.map +1 -1
- package/package.json +4 -2
- package/dist/core/tool-builder.d.ts +0 -130
- package/dist/core/tool-builder.d.ts.map +0 -1
- package/dist/core/tool-builder.js +0 -343
- package/dist/core/tool-builder.js.map +0 -1
- package/dist/node-builder.d.ts +0 -64
- package/dist/node-builder.d.ts.map +0 -1
- package/dist/node-builder.js +0 -261
- package/dist/node-builder.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,646 +1,1119 @@
|
|
|
1
1
|
# @graph-compose/client
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
This package works hand-in-hand with `@graph-compose/core`, which defines the underlying structure and validation schemas for workflows. You'll use `@graph-compose/client` to programmatically construct these workflow graphs, starting with basic HTTP operations and dependencies.
|
|
6
|
-
|
|
3
|
+
A fluent TypeScript client for building declarative, Temporal-orchestrated workflows. Compose HTTP-based microservices or multi-agent AI systems with a simple, type-safe API.
|
|
7
4
|
|
|
8
5
|
## Features
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* ⏱️ **Timeouts & Retries:** Configure detailed retry policies and timeouts (`startToClose`, `scheduleToClose`) for nodes and tools.
|
|
18
|
-
* ⚙️ **Workflow Configuration:** Set global workflow settings like execution timeouts (`withWorkflowConfig`).
|
|
19
|
-
* 🔀 **Conditional Execution:** Define complex branching logic for HTTP nodes (`withConditions`).
|
|
20
|
-
* 🛡️ **Schema Validation:** Apply Zod-based input/output validation schemas to HTTP nodes (`withValidation`).
|
|
21
|
-
|
|
22
|
-
## API Reference & Documentation
|
|
23
|
-
|
|
24
|
-
> **Note:** This document focuses specifically on the `@graph-compose/client` SDK for building workflows programmatically. For broader platform documentation, conceptual guides, API schemas, and examples, please visit the main Graph Compose site: [graphcompose.io](https://graphcompose.io).
|
|
25
|
-
|
|
26
|
-
* **Guides & Overviews:** For conceptual guides, integration examples, and broader platform documentation, visit the main Graph Compose documentation site: [graphcompose.io/docs](https://graphcompose.io/docs).
|
|
27
|
-
* **Workflow API Schemas:** For the definitive source of truth on the backend Workflow API request/response schemas (based on Zod/OpenAPI), see: [graphcompose.io/workflows](https://graphcompose.io/workflows).
|
|
28
|
-
|
|
29
|
-
## Table of Contents
|
|
30
|
-
|
|
31
|
-
- [Features](#features)
|
|
32
|
-
- [API Reference & Documentation](#api-reference--documentation)
|
|
33
|
-
- [Installation](#installation)
|
|
34
|
-
- [Quick Start: Building a Simple HTTP Workflow](#quick-start-building-a-simple-http-workflow)
|
|
35
|
-
- [Method Summary](#method-summary)
|
|
36
|
-
- [Core Concepts](#core-concepts)
|
|
37
|
-
- [Defining Node Types](#defining-node-types)
|
|
38
|
-
- [Defining Tool Types (for Agents)](#defining-tool-types-for-agents)
|
|
39
|
-
- [Workflow Configuration](#workflow-configuration)
|
|
40
|
-
- [Validation](#validation)
|
|
41
|
-
- [Execution](#execution)
|
|
42
|
-
- [Getting the Workflow Definition](#getting-the-workflow-definition)
|
|
43
|
-
- [Interacting with Existing Workflows (API)](#interacting-with-existing-workflows-api)
|
|
44
|
-
- [Full Example: Putting it Together](#full-example-putting-it-together)
|
|
45
|
-
- [Contributing](#contributing)
|
|
46
|
-
- [License](#license)
|
|
7
|
+
- 🔗 **HTTP Workflow Orchestration** - Chain HTTP services with dependency management
|
|
8
|
+
- 🤖 **Multi-Agent AI Workflows** - Build ADK (Agent Development Kit) hierarchies with LLMs, tools, and orchestrators
|
|
9
|
+
- 🎯 **Type-Safe Builder Pattern** - Fluent API with full TypeScript support
|
|
10
|
+
- ✨ **JSONata Templating** - Dynamic parameter resolution with `{{ }}` expressions
|
|
11
|
+
- 🔄 **Automatic Retry & Error Handling** - Configure retry policies and error boundaries
|
|
12
|
+
- ✅ **Built-in Validation** - Zod-based schema validation for workflows
|
|
13
|
+
- 🧪 **Temporal Integration** - Native support for Temporal workflow execution
|
|
47
14
|
|
|
48
15
|
## Installation
|
|
49
16
|
|
|
50
17
|
```bash
|
|
51
18
|
npm install @graph-compose/client @graph-compose/core
|
|
52
19
|
# or
|
|
20
|
+
pnpm add @graph-compose/client @graph-compose/core
|
|
21
|
+
# or
|
|
53
22
|
yarn add @graph-compose/client @graph-compose/core
|
|
54
23
|
```
|
|
55
|
-
*(Note: `@graph-compose/core` is a required peer dependency.)*
|
|
56
24
|
|
|
57
|
-
##
|
|
25
|
+
## Authentication
|
|
58
26
|
|
|
59
|
-
|
|
60
|
-
import { GraphCompose } from '@graph-compose/client';
|
|
61
|
-
import { z } from 'zod'; // Often used with core schemas
|
|
27
|
+
The client requires an API token to execute workflows against the GraphCompose API:
|
|
62
28
|
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
const
|
|
66
|
-
token:
|
|
29
|
+
```typescript
|
|
30
|
+
// Option 1: Pass token in constructor
|
|
31
|
+
const workflow = new GraphCompose({
|
|
32
|
+
token: 'your-api-token'
|
|
67
33
|
});
|
|
68
34
|
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.get('https://api.example.com/users/{{ context.userId }}')
|
|
74
|
-
.withHeaders({ 'X-Request-ID': '{{ $uuid() }}' })
|
|
75
|
-
.withStartToCloseTimeout('5s')
|
|
76
|
-
.end(); // <-- Required: Finalize the 'fetch_user' node definition
|
|
77
|
-
|
|
78
|
-
// Node 2: Fetch user's orders, depending on the first node
|
|
79
|
-
graph.node('fetch_orders')
|
|
80
|
-
.withDependencies(['fetch_user']) // This node runs after 'fetch_user' completes
|
|
81
|
-
.get('https://api.example.com/orders?userId={{ results.fetch_user.id }}')
|
|
82
|
-
.end(); // <-- Required: Finalize the 'fetch_orders' node definition
|
|
83
|
-
|
|
84
|
-
// Node 3: Post-process orders
|
|
85
|
-
graph.node('process_orders')
|
|
86
|
-
.withDependencies(['fetch_orders']) // Depends on 'fetch_orders'
|
|
87
|
-
.post('https://api.example.com/process')
|
|
88
|
-
.withBody({
|
|
89
|
-
orderData: '{{ results.fetch_orders }}',
|
|
90
|
-
processingTimestamp: '{{ $now() }}'
|
|
91
|
-
})
|
|
92
|
-
.end(); // <-- Required: Finalize the 'process_orders' node definition
|
|
35
|
+
// Option 2: Set environment variable
|
|
36
|
+
// GRAPH_COMPOSE_TOKEN=your-api-token
|
|
37
|
+
const workflow = new GraphCompose();
|
|
38
|
+
```
|
|
93
39
|
|
|
94
|
-
|
|
95
|
-
// Requires all nodes/tools to be finalized with .end()
|
|
96
|
-
const validation = graph.validate();
|
|
97
|
-
if (!validation.isValid) {
|
|
98
|
-
console.error("Workflow validation failed:", validation.errors);
|
|
99
|
-
throw new Error("Invalid workflow definition");
|
|
100
|
-
} else {
|
|
101
|
-
console.log("Local validation successful!");
|
|
102
|
-
}
|
|
40
|
+
**API Endpoint:** `https://api.graphcompose.io/api/v1`
|
|
103
41
|
|
|
104
|
-
|
|
105
|
-
// Requires all nodes/tools to be finalized with .end()
|
|
106
|
-
console.log('Executing workflow...');
|
|
107
|
-
const apiResponse = await graph.execute({
|
|
108
|
-
context: { // Initial data for the workflow run
|
|
109
|
-
userId: 'user-789'
|
|
110
|
-
},
|
|
111
|
-
// webhookUrl: 'https://my-service.com/updates' // Optional webhook
|
|
112
|
-
});
|
|
42
|
+
Get your API token from the [GraphCompose Dashboard](https://app.graphcompose.io)
|
|
113
43
|
|
|
114
|
-
|
|
115
|
-
console.log('Workflow execution started successfully:', apiResponse.data);
|
|
116
|
-
// Access workflowId via apiResponse.data.workflowId
|
|
117
|
-
} else {
|
|
118
|
-
console.error(`Workflow execution failed: ${apiResponse.message}`);
|
|
119
|
-
}
|
|
44
|
+
## Quick Start
|
|
120
45
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
46
|
+
### End-to-End Example
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { GraphCompose } from '@graph-compose/client';
|
|
50
|
+
|
|
51
|
+
// 1. Build your workflow
|
|
52
|
+
const workflow = new GraphCompose({
|
|
53
|
+
token: process.env.GRAPH_COMPOSE_TOKEN
|
|
54
|
+
})
|
|
55
|
+
.http('fetchData')
|
|
56
|
+
.get('https://api.example.com/data')
|
|
57
|
+
.end()
|
|
58
|
+
.http('processData')
|
|
59
|
+
.post('https://api.example.com/process')
|
|
60
|
+
.withBody({ data: '{{results.fetchData}}' })
|
|
61
|
+
.dependsOn(['fetchData'])
|
|
62
|
+
.end();
|
|
63
|
+
|
|
64
|
+
// 2. Validate it
|
|
65
|
+
const validation = workflow.validate();
|
|
66
|
+
if (!validation.isValid) {
|
|
67
|
+
throw new Error('Invalid workflow');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 3. Execute it
|
|
71
|
+
const result = await workflow.execute({
|
|
72
|
+
context: { userId: '123' }
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// 4. Poll for completion and get results
|
|
76
|
+
if (result.success && result.data) {
|
|
77
|
+
const workflowId = result.data.workflowId;
|
|
78
|
+
|
|
79
|
+
// Poll until complete
|
|
80
|
+
let status = 'RUNNING';
|
|
81
|
+
while (status === 'RUNNING') {
|
|
82
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
83
|
+
const statusResult = await workflow.getWorkflowStatus(workflowId);
|
|
84
|
+
if (statusResult.success && statusResult.data) {
|
|
85
|
+
status = statusResult.data.status;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Get final results
|
|
90
|
+
const finalResult = await workflow.getWorkflowStatus(workflowId);
|
|
91
|
+
if (finalResult.success && finalResult.data?.status === 'COMPLETED') {
|
|
92
|
+
console.log('Results:', finalResult.data.execution_state);
|
|
126
93
|
}
|
|
127
94
|
}
|
|
128
95
|
```
|
|
129
96
|
|
|
97
|
+
### Traditional HTTP Workflow
|
|
130
98
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
This section provides a quick overview of the primary methods available on the `GraphCompose` instance, grouped by purpose.
|
|
99
|
+
Build a workflow that chains multiple HTTP services:
|
|
134
100
|
|
|
135
|
-
|
|
101
|
+
```typescript
|
|
102
|
+
import { GraphCompose } from '@graph-compose/client';
|
|
136
103
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
104
|
+
const workflow = new GraphCompose({
|
|
105
|
+
token: 'your-api-token'
|
|
106
|
+
})
|
|
107
|
+
.http('fetchUser')
|
|
108
|
+
.get('https://api.example.com/users/{{userId}}')
|
|
109
|
+
.withHeaders({
|
|
110
|
+
'Authorization': 'Bearer {{$secret("api_token")}}'
|
|
111
|
+
})
|
|
112
|
+
.end()
|
|
113
|
+
.http('enrichProfile')
|
|
114
|
+
.post('https://api.example.com/enrich')
|
|
115
|
+
.withBody({
|
|
116
|
+
userId: '{{results.fetchUser.id}}',
|
|
117
|
+
email: '{{results.fetchUser.email}}'
|
|
118
|
+
})
|
|
119
|
+
.dependsOn(['fetchUser'])
|
|
120
|
+
.end()
|
|
121
|
+
.build();
|
|
122
|
+
```
|
|
140
123
|
|
|
141
|
-
###
|
|
124
|
+
### ADK Multi-Agent Workflow
|
|
142
125
|
|
|
143
|
-
|
|
126
|
+
Build an AI agent workflow with tools and orchestration:
|
|
144
127
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
128
|
+
```typescript
|
|
129
|
+
import {
|
|
130
|
+
GraphCompose,
|
|
131
|
+
AdkWorkflowBuilder,
|
|
132
|
+
createLlmAgent,
|
|
133
|
+
createHttpTool
|
|
134
|
+
} from '@graph-compose/client';
|
|
135
|
+
|
|
136
|
+
// Define the agent workflow
|
|
137
|
+
const agentWorkflow = new AdkWorkflowBuilder()
|
|
138
|
+
.agent(createLlmAgent({
|
|
139
|
+
id: 'assistant',
|
|
140
|
+
httpConfig: {
|
|
141
|
+
url: 'https://api.example.com/llm',
|
|
142
|
+
method: 'POST'
|
|
143
|
+
},
|
|
144
|
+
tools: ['searchTool'],
|
|
145
|
+
outputKey: 'assistant_response'
|
|
146
|
+
}))
|
|
147
|
+
.httpTool(createHttpTool({
|
|
148
|
+
id: 'searchTool',
|
|
149
|
+
httpConfig: {
|
|
150
|
+
url: 'https://api.example.com/search',
|
|
151
|
+
method: 'POST',
|
|
152
|
+
body: {
|
|
153
|
+
query: '{{state.user_query}}'
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}))
|
|
157
|
+
.setRootAgent('assistant')
|
|
158
|
+
.build();
|
|
159
|
+
|
|
160
|
+
// Create the workflow node
|
|
161
|
+
const workflow = new GraphCompose()
|
|
162
|
+
.adk('aiAgent')
|
|
163
|
+
.withWorkflow(agentWorkflow)
|
|
164
|
+
.withInitialPrompt('Hello! How can I help you today?')
|
|
165
|
+
.withMaxCycles(10)
|
|
166
|
+
.end()
|
|
167
|
+
.build();
|
|
168
|
+
```
|
|
151
169
|
|
|
152
|
-
|
|
170
|
+
## HTTP Workflow Builder
|
|
153
171
|
|
|
154
|
-
|
|
172
|
+
### Basic HTTP Methods
|
|
155
173
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
```typescript
|
|
175
|
+
const builder = new GraphCompose();
|
|
176
|
+
|
|
177
|
+
// GET request
|
|
178
|
+
builder.http('getUser')
|
|
179
|
+
.get('https://api.example.com/users/123')
|
|
180
|
+
.end();
|
|
181
|
+
|
|
182
|
+
// POST request
|
|
183
|
+
builder.http('createUser')
|
|
184
|
+
.post('https://api.example.com/users')
|
|
185
|
+
.withBody({ name: 'John', email: 'john@example.com' })
|
|
186
|
+
.end();
|
|
187
|
+
|
|
188
|
+
// PUT, PATCH, DELETE
|
|
189
|
+
builder.http('updateUser').put('...').end();
|
|
190
|
+
builder.http('patchUser').patch('...').end();
|
|
191
|
+
builder.http('deleteUser').delete('...').end();
|
|
192
|
+
```
|
|
172
193
|
|
|
173
|
-
###
|
|
194
|
+
### Headers and Authentication
|
|
174
195
|
|
|
175
|
-
|
|
196
|
+
```typescript
|
|
197
|
+
builder.http('secureRequest')
|
|
198
|
+
.post('https://api.example.com/data')
|
|
199
|
+
.withHeaders({
|
|
200
|
+
'Authorization': 'Bearer {{$secret("api_token")}}',
|
|
201
|
+
'X-Request-ID': '{{context.requestId}}',
|
|
202
|
+
'Content-Type': 'application/json'
|
|
203
|
+
})
|
|
204
|
+
.end();
|
|
205
|
+
```
|
|
176
206
|
|
|
177
|
-
|
|
178
|
-
| :------- | :-------------------------------------------------------------------- |
|
|
179
|
-
| `.end()` | Finalizes the current node/tool definition and adds it to the graph. |
|
|
207
|
+
### Dynamic Body and Parameters
|
|
180
208
|
|
|
181
|
-
|
|
209
|
+
```typescript
|
|
210
|
+
builder.http('processOrder')
|
|
211
|
+
.post('https://api.example.com/orders')
|
|
212
|
+
.withBody({
|
|
213
|
+
orderId: '{{results.createOrder.id}}',
|
|
214
|
+
userId: '{{context.userId}}',
|
|
215
|
+
items: '{{results.fetchCart.items}}',
|
|
216
|
+
total: '{{results.calculateTotal.amount}}'
|
|
217
|
+
})
|
|
218
|
+
.withParams({
|
|
219
|
+
async: 'true',
|
|
220
|
+
priority: '{{context.priority}}'
|
|
221
|
+
})
|
|
222
|
+
.end();
|
|
223
|
+
```
|
|
182
224
|
|
|
183
|
-
|
|
225
|
+
### Dependencies and Ordering
|
|
184
226
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
227
|
+
```typescript
|
|
228
|
+
// Sequential execution
|
|
229
|
+
builder
|
|
230
|
+
.http('step1').get('...').end()
|
|
231
|
+
.http('step2').post('...').dependsOn(['step1']).end()
|
|
232
|
+
.http('step3').post('...').dependsOn(['step2']).end();
|
|
233
|
+
|
|
234
|
+
// Parallel execution (no dependencies)
|
|
235
|
+
builder
|
|
236
|
+
.http('fetchUserData').get('...').end()
|
|
237
|
+
.http('fetchOrderData').get('...').end()
|
|
238
|
+
.http('fetchPreferences').get('...').end();
|
|
239
|
+
|
|
240
|
+
// Fan-out then fan-in
|
|
241
|
+
builder
|
|
242
|
+
.http('fetchData1').get('...').end()
|
|
243
|
+
.http('fetchData2').get('...').end()
|
|
244
|
+
.http('aggregate')
|
|
245
|
+
.post('...')
|
|
246
|
+
.dependsOn(['fetchData1', 'fetchData2'])
|
|
247
|
+
.end();
|
|
248
|
+
```
|
|
190
249
|
|
|
191
|
-
###
|
|
250
|
+
### Activity Configuration
|
|
192
251
|
|
|
193
|
-
|
|
252
|
+
Configure Temporal activity behavior:
|
|
194
253
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
254
|
+
```typescript
|
|
255
|
+
builder.http('reliableCall')
|
|
256
|
+
.post('https://api.example.com/process')
|
|
257
|
+
.withStartToCloseTimeout('30s')
|
|
258
|
+
.withScheduleToCloseTimeout('5m')
|
|
259
|
+
.withRetries({
|
|
260
|
+
maximumAttempts: 5,
|
|
261
|
+
initialInterval: '1s',
|
|
262
|
+
backoffCoefficient: 2.0,
|
|
263
|
+
maximumInterval: '30s'
|
|
264
|
+
})
|
|
265
|
+
.end();
|
|
266
|
+
```
|
|
199
267
|
|
|
200
|
-
###
|
|
268
|
+
### Error Boundaries
|
|
201
269
|
|
|
202
|
-
|
|
270
|
+
Wrap nodes with error handling:
|
|
203
271
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
272
|
+
```typescript
|
|
273
|
+
builder
|
|
274
|
+
.http('riskyOperation')
|
|
275
|
+
.post('https://api.example.com/risky')
|
|
276
|
+
.end()
|
|
277
|
+
.errorBoundary('handleError')
|
|
278
|
+
.protects(['riskyOperation'])
|
|
279
|
+
.onError(
|
|
280
|
+
builder => builder
|
|
281
|
+
.http('fallback')
|
|
282
|
+
.post('https://api.example.com/fallback')
|
|
283
|
+
.end()
|
|
284
|
+
)
|
|
285
|
+
.end();
|
|
286
|
+
```
|
|
208
287
|
|
|
209
|
-
###
|
|
288
|
+
### Conditional Branching
|
|
210
289
|
|
|
211
|
-
|
|
290
|
+
```typescript
|
|
291
|
+
builder
|
|
292
|
+
.http('checkStatus')
|
|
293
|
+
.get('https://api.example.com/status')
|
|
294
|
+
.end()
|
|
295
|
+
.http('handleSuccess')
|
|
296
|
+
.post('...')
|
|
297
|
+
.runWhen({
|
|
298
|
+
condition: '{{results.checkStatus.state = "completed"}}'
|
|
299
|
+
})
|
|
300
|
+
.dependsOn(['checkStatus'])
|
|
301
|
+
.end()
|
|
302
|
+
.http('handleFailure')
|
|
303
|
+
.post('...')
|
|
304
|
+
.runWhen({
|
|
305
|
+
condition: '{{results.checkStatus.state = "failed"}}'
|
|
306
|
+
})
|
|
307
|
+
.dependsOn(['checkStatus'])
|
|
308
|
+
.end();
|
|
309
|
+
```
|
|
212
310
|
|
|
213
|
-
|
|
214
|
-
| :-------------- | :------------------------------------------------------------------------ |
|
|
215
|
-
| `getWorkflow()` | Returns the complete workflow definition object. |
|
|
216
|
-
| `toJSON()` | Alias for `getWorkflow()`; standard method for `JSON.stringify()`. |
|
|
311
|
+
## ADK Agent Workflows
|
|
217
312
|
|
|
218
|
-
###
|
|
313
|
+
### Agent Types
|
|
219
314
|
|
|
220
|
-
|
|
315
|
+
#### LLM Agent
|
|
316
|
+
The primary agent that interacts with language models:
|
|
221
317
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
318
|
+
```typescript
|
|
319
|
+
import { createLlmAgent } from '@graph-compose/client';
|
|
320
|
+
|
|
321
|
+
createLlmAgent({
|
|
322
|
+
id: 'assistant',
|
|
323
|
+
httpConfig: {
|
|
324
|
+
url: 'https://api.example.com/llm',
|
|
325
|
+
method: 'POST'
|
|
326
|
+
},
|
|
327
|
+
tools: ['searchTool', 'calculatorTool'],
|
|
328
|
+
outputKey: 'assistant_response',
|
|
329
|
+
activityConfig: {
|
|
330
|
+
startToCloseTimeout: '30s',
|
|
331
|
+
retryPolicy: {
|
|
332
|
+
maximumAttempts: 3
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
```
|
|
226
337
|
|
|
227
|
-
|
|
338
|
+
#### Sequential Agent
|
|
339
|
+
Executes sub-agents in order:
|
|
228
340
|
|
|
229
|
-
|
|
341
|
+
```typescript
|
|
342
|
+
import { createSequentialAgent, createSubAgentReference } from '@graph-compose/client';
|
|
343
|
+
|
|
344
|
+
createSequentialAgent({
|
|
345
|
+
id: 'pipeline',
|
|
346
|
+
httpConfig: {
|
|
347
|
+
url: 'https://api.example.com/orchestrate',
|
|
348
|
+
method: 'POST'
|
|
349
|
+
},
|
|
350
|
+
subAgents: [
|
|
351
|
+
createSubAgentReference('extractor'),
|
|
352
|
+
createSubAgentReference('transformer'),
|
|
353
|
+
createSubAgentReference('loader')
|
|
354
|
+
]
|
|
355
|
+
})
|
|
356
|
+
```
|
|
230
357
|
|
|
231
|
-
|
|
358
|
+
#### Parallel Agent
|
|
359
|
+
Executes sub-agents concurrently:
|
|
232
360
|
|
|
233
361
|
```typescript
|
|
234
|
-
import {
|
|
362
|
+
import { createParallelAgent } from '@graph-compose/client';
|
|
363
|
+
|
|
364
|
+
createParallelAgent({
|
|
365
|
+
id: 'fanOut',
|
|
366
|
+
httpConfig: {
|
|
367
|
+
url: 'https://api.example.com/orchestrate',
|
|
368
|
+
method: 'POST'
|
|
369
|
+
},
|
|
370
|
+
subAgents: [
|
|
371
|
+
createSubAgentReference('analyzer1'),
|
|
372
|
+
createSubAgentReference('analyzer2'),
|
|
373
|
+
createSubAgentReference('analyzer3')
|
|
374
|
+
]
|
|
375
|
+
// Note: ParallelAgent does not support outputKey
|
|
376
|
+
// Individual sub-agents should have their own outputKeys
|
|
377
|
+
})
|
|
378
|
+
```
|
|
235
379
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
380
|
+
#### Loop Agent
|
|
381
|
+
Repeats sub-agents with iteration limits:
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
import { createLoopAgent } from '@graph-compose/client';
|
|
385
|
+
|
|
386
|
+
createLoopAgent({
|
|
387
|
+
id: 'retryLoop',
|
|
388
|
+
httpConfig: {
|
|
389
|
+
url: 'https://api.example.com/orchestrate',
|
|
390
|
+
method: 'POST'
|
|
391
|
+
},
|
|
392
|
+
subAgents: [
|
|
393
|
+
createSubAgentReference('worker')
|
|
394
|
+
],
|
|
395
|
+
maxAgentLoopIterations: 5,
|
|
396
|
+
loopExitCondition: '{{state.task_completed = true}}',
|
|
397
|
+
outputKey: 'loop_result'
|
|
398
|
+
})
|
|
240
399
|
```
|
|
241
400
|
|
|
242
|
-
###
|
|
401
|
+
### Tools
|
|
243
402
|
|
|
244
|
-
|
|
403
|
+
#### HTTP Tool
|
|
404
|
+
Execute external HTTP services:
|
|
245
405
|
|
|
246
|
-
|
|
247
|
-
|
|
406
|
+
```typescript
|
|
407
|
+
import { createHttpTool } from '@graph-compose/client';
|
|
408
|
+
|
|
409
|
+
createHttpTool({
|
|
410
|
+
id: 'searchTool',
|
|
411
|
+
httpConfig: {
|
|
412
|
+
url: 'https://api.example.com/search',
|
|
413
|
+
method: 'POST',
|
|
414
|
+
body: {
|
|
415
|
+
query: '{{state.user_query}}',
|
|
416
|
+
filters: '{{state.filters}}'
|
|
417
|
+
},
|
|
418
|
+
headers: {
|
|
419
|
+
'Authorization': 'Bearer {{$secret("search_api_key")}}'
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
outputKey: 'search_results'
|
|
423
|
+
})
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
#### Agent Tool
|
|
427
|
+
Delegate to another agent (specialist pattern):
|
|
248
428
|
|
|
249
429
|
```typescript
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
430
|
+
import { createAgentTool } from '@graph-compose/client';
|
|
431
|
+
|
|
432
|
+
createAgentTool({
|
|
433
|
+
id: 'specialistTool',
|
|
434
|
+
targetAgentId: 'specialist',
|
|
435
|
+
skipSummarization: false,
|
|
436
|
+
outputKey: 'specialist_result'
|
|
437
|
+
})
|
|
438
|
+
```
|
|
256
439
|
|
|
257
|
-
|
|
258
|
-
.node('process_data') // Start defining node 'process_data'
|
|
259
|
-
.withDependencies(['fetch_data']) // Depends on 'fetch_data'
|
|
260
|
-
.post('https://api.example.com/process')
|
|
261
|
-
.withBody({ input: '{{ results.fetch_data }}' })
|
|
262
|
-
.end(); // Finalize 'process_data'
|
|
440
|
+
### Complete ADK Example
|
|
263
441
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
442
|
+
```typescript
|
|
443
|
+
import {
|
|
444
|
+
GraphCompose,
|
|
445
|
+
AdkWorkflowBuilder,
|
|
446
|
+
createLlmAgent,
|
|
447
|
+
createSequentialAgent,
|
|
448
|
+
createHttpTool,
|
|
449
|
+
createAgentTool,
|
|
450
|
+
createSubAgentReference
|
|
451
|
+
} from '@graph-compose/client';
|
|
452
|
+
|
|
453
|
+
// Build the agent hierarchy
|
|
454
|
+
const workflow = new AdkWorkflowBuilder()
|
|
455
|
+
// Main coordinator agent
|
|
456
|
+
.agent(createLlmAgent({
|
|
457
|
+
id: 'coordinator',
|
|
458
|
+
httpConfig: {
|
|
459
|
+
url: 'https://api.example.com/llm',
|
|
460
|
+
method: 'POST'
|
|
461
|
+
},
|
|
462
|
+
tools: ['researchTool', 'analysisTool'],
|
|
463
|
+
outputKey: 'coordinator_response'
|
|
464
|
+
}))
|
|
465
|
+
|
|
466
|
+
// Research specialist
|
|
467
|
+
.agent(createLlmAgent({
|
|
468
|
+
id: 'researcher',
|
|
469
|
+
httpConfig: {
|
|
470
|
+
url: 'https://api.example.com/llm',
|
|
471
|
+
method: 'POST'
|
|
472
|
+
},
|
|
473
|
+
tools: ['searchTool'],
|
|
474
|
+
outputKey: 'research_results'
|
|
475
|
+
}))
|
|
476
|
+
|
|
477
|
+
// Analysis specialist
|
|
478
|
+
.agent(createLlmAgent({
|
|
479
|
+
id: 'analyst',
|
|
480
|
+
httpConfig: {
|
|
481
|
+
url: 'https://api.example.com/llm',
|
|
482
|
+
method: 'POST'
|
|
483
|
+
},
|
|
484
|
+
outputKey: 'analysis_results'
|
|
485
|
+
}))
|
|
486
|
+
|
|
487
|
+
// HTTP search tool
|
|
488
|
+
.httpTool(createHttpTool({
|
|
489
|
+
id: 'searchTool',
|
|
490
|
+
httpConfig: {
|
|
491
|
+
url: 'https://api.example.com/search',
|
|
492
|
+
method: 'POST'
|
|
493
|
+
},
|
|
494
|
+
outputKey: 'search_data'
|
|
495
|
+
}))
|
|
496
|
+
|
|
497
|
+
// Agent tools for delegation
|
|
498
|
+
.agentTool(createAgentTool({
|
|
499
|
+
id: 'researchTool',
|
|
500
|
+
targetAgentId: 'researcher',
|
|
501
|
+
skipSummarization: false
|
|
502
|
+
}))
|
|
503
|
+
|
|
504
|
+
.agentTool(createAgentTool({
|
|
505
|
+
id: 'analysisTool',
|
|
506
|
+
targetAgentId: 'analyst',
|
|
507
|
+
skipSummarization: false
|
|
508
|
+
}))
|
|
509
|
+
|
|
510
|
+
// Set the entry point
|
|
511
|
+
.setRootAgent('coordinator')
|
|
512
|
+
|
|
513
|
+
// Configure workflow-level settings
|
|
514
|
+
.withMaxCycles(20)
|
|
515
|
+
|
|
516
|
+
.build();
|
|
517
|
+
|
|
518
|
+
// Create the workflow
|
|
519
|
+
const graph = new GraphCompose()
|
|
520
|
+
.adk('aiWorkflow')
|
|
521
|
+
.withWorkflow(workflow)
|
|
522
|
+
.withInitialPrompt('Research and analyze recent AI trends')
|
|
523
|
+
.withState({
|
|
524
|
+
user_id: 'user_123',
|
|
525
|
+
context: 'technology'
|
|
526
|
+
})
|
|
527
|
+
.end()
|
|
528
|
+
.build();
|
|
267
529
|
```
|
|
268
530
|
|
|
269
|
-
##
|
|
531
|
+
## Advanced Patterns
|
|
270
532
|
|
|
271
|
-
|
|
533
|
+
### Agent Handoff Pattern
|
|
272
534
|
|
|
273
|
-
|
|
535
|
+
Nested sub-agents for dynamic routing:
|
|
274
536
|
|
|
275
|
-
|
|
537
|
+
```typescript
|
|
538
|
+
createLlmAgent({
|
|
539
|
+
id: 'router',
|
|
540
|
+
httpConfig: { url: '...', method: 'POST' },
|
|
541
|
+
subAgents: [
|
|
542
|
+
// Nested agents for handoff
|
|
543
|
+
createLlmAgent({
|
|
544
|
+
id: 'sales_specialist',
|
|
545
|
+
httpConfig: { url: '...', method: 'POST' }
|
|
546
|
+
}),
|
|
547
|
+
createLlmAgent({
|
|
548
|
+
id: 'support_specialist',
|
|
549
|
+
httpConfig: { url: '...', method: 'POST' }
|
|
550
|
+
})
|
|
551
|
+
]
|
|
552
|
+
})
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Human-in-the-Loop (HITL)
|
|
556
|
+
|
|
557
|
+
Agents can request human approval:
|
|
276
558
|
|
|
277
559
|
```typescript
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
560
|
+
// Your agent HTTP service returns:
|
|
561
|
+
{
|
|
562
|
+
"content": [{"type": "text", "text": "Requesting approval..."}],
|
|
563
|
+
"hitl_request": {
|
|
564
|
+
"type": "approval",
|
|
565
|
+
"message": "Should I proceed with this action?",
|
|
566
|
+
"options": ["approve", "reject", "modify"]
|
|
567
|
+
}
|
|
568
|
+
}
|
|
284
569
|
```
|
|
285
570
|
|
|
286
|
-
###
|
|
571
|
+
### State Management
|
|
287
572
|
|
|
288
|
-
|
|
573
|
+
Access shared state across agents:
|
|
289
574
|
|
|
290
575
|
```typescript
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
576
|
+
// Initialize state
|
|
577
|
+
.withState({
|
|
578
|
+
user_preferences: { theme: 'dark' },
|
|
579
|
+
session_data: { authenticated: true }
|
|
580
|
+
})
|
|
581
|
+
|
|
582
|
+
// Access in agent HTTP configs
|
|
583
|
+
.withBody({
|
|
584
|
+
user_theme: '{{state.user_preferences.theme}}',
|
|
585
|
+
auth_status: '{{state.session_data.authenticated}}'
|
|
586
|
+
})
|
|
587
|
+
|
|
588
|
+
// Agents save to state via outputKey
|
|
589
|
+
.agent(createLlmAgent({
|
|
590
|
+
id: 'processor',
|
|
591
|
+
outputKey: 'processed_data', // Saved to state.processed_data
|
|
592
|
+
// ...
|
|
593
|
+
}))
|
|
299
594
|
```
|
|
300
595
|
|
|
301
|
-
###
|
|
596
|
+
### Exit Flow Control
|
|
302
597
|
|
|
303
|
-
|
|
598
|
+
Agents can terminate the workflow early:
|
|
304
599
|
|
|
305
600
|
```typescript
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
errorMessage: '{{ error.message }}',
|
|
312
|
-
timestamp: '{{ $now() }}'
|
|
313
|
-
})
|
|
314
|
-
.end(); // Required!
|
|
601
|
+
// Your agent HTTP service returns:
|
|
602
|
+
{
|
|
603
|
+
"content": [{"type": "text", "text": "Task completed!"}],
|
|
604
|
+
"exit_flow": true // Stops workflow execution
|
|
605
|
+
}
|
|
315
606
|
```
|
|
316
607
|
|
|
317
|
-
##
|
|
608
|
+
## JSONata Templating
|
|
318
609
|
|
|
319
|
-
|
|
610
|
+
All string fields support JSONata expressions with `{{ }}` delimiters:
|
|
320
611
|
|
|
321
|
-
###
|
|
612
|
+
### Accessing Results
|
|
322
613
|
|
|
323
|
-
|
|
614
|
+
```typescript
|
|
615
|
+
'{{results.fetchUser.id}}' // Node result
|
|
616
|
+
'{{results.fetchUser.profile.name}}' // Nested property
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
### Accessing Context
|
|
324
620
|
|
|
325
621
|
```typescript
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
.withBody({ query: "{{input.query}}" }) // 'input' contains agent-provided parameters
|
|
330
|
-
.withScheduleToCloseTimeout('15s') // Example timeout
|
|
331
|
-
.end(); // Required!
|
|
622
|
+
'{{context.userId}}' // Workflow context
|
|
623
|
+
'{{context.tenantId}}' // Any context value
|
|
624
|
+
```
|
|
332
625
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
626
|
+
### Accessing State (ADK)
|
|
627
|
+
|
|
628
|
+
```typescript
|
|
629
|
+
'{{state.user_query}}' // Session state
|
|
630
|
+
'{{state.preferences.language}}' // Nested state
|
|
338
631
|
```
|
|
339
632
|
|
|
340
|
-
###
|
|
633
|
+
### Secrets
|
|
341
634
|
|
|
342
|
-
|
|
635
|
+
```typescript
|
|
636
|
+
'{{$secret("api_key")}}' // Secret reference
|
|
637
|
+
'Bearer {{$secret("auth_token")}}' // In strings
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### JSONata Operators
|
|
343
641
|
|
|
344
642
|
```typescript
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
.end(); // End sub-graph node
|
|
359
|
-
}) // End of sub-graph definition
|
|
360
|
-
.end(); // <-- Required: Finalize the tool itself
|
|
643
|
+
// Conditionals
|
|
644
|
+
'{{results.status.code = 200 ? "success" : "failure"}}'
|
|
645
|
+
|
|
646
|
+
// Arithmetic
|
|
647
|
+
'{{results.price.amount * 1.1}}'
|
|
648
|
+
|
|
649
|
+
// String operations
|
|
650
|
+
'{{$uppercase(results.user.name)}}'
|
|
651
|
+
'{{$substring(results.text, 0, 100)}}'
|
|
652
|
+
|
|
653
|
+
// Array operations
|
|
654
|
+
'{{$count(results.items)}}'
|
|
655
|
+
'{{results.items[0].id}}'
|
|
361
656
|
```
|
|
362
657
|
|
|
363
|
-
|
|
364
|
-
- Graph tools can only contain HTTP nodes.
|
|
365
|
-
- Graph tools must have at least one node defined (and ended).
|
|
366
|
-
- Each graph tool runs as an isolated sub-workflow when invoked by an agent.
|
|
658
|
+
## Validation
|
|
367
659
|
|
|
368
|
-
###
|
|
660
|
+
### Automatic Validation
|
|
369
661
|
|
|
370
|
-
|
|
662
|
+
The builder performs comprehensive validation:
|
|
371
663
|
|
|
372
664
|
```typescript
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
665
|
+
const workflow = builder.build();
|
|
666
|
+
const validation = builder.validate();
|
|
667
|
+
|
|
668
|
+
if (!validation.isValid) {
|
|
669
|
+
validation.errors.forEach(err => {
|
|
670
|
+
console.error(err.message);
|
|
671
|
+
});
|
|
672
|
+
}
|
|
379
673
|
```
|
|
380
674
|
|
|
381
|
-
|
|
675
|
+
### Validation Checks
|
|
382
676
|
|
|
383
|
-
|
|
677
|
+
- ✅ **Node IDs**: Unique and valid format
|
|
678
|
+
- ✅ **Dependencies**: Valid references, no circular deps
|
|
679
|
+
- ✅ **HTTP Config**: Valid URLs, methods, headers
|
|
680
|
+
- ✅ **JSONata**: Syntax validation in templates
|
|
681
|
+
- ✅ **ADK Structure**: Agent/tool references, type constraints
|
|
682
|
+
- ✅ **Schema Compliance**: Zod validation against core schemas
|
|
384
683
|
|
|
385
|
-
|
|
386
|
-
// Assuming previous node/tool builders have called .end()
|
|
684
|
+
### Manual Validation
|
|
387
685
|
|
|
388
|
-
|
|
389
|
-
graph
|
|
390
|
-
userId: "user-123",
|
|
391
|
-
userInput: "Tell me about GraphCompose",
|
|
392
|
-
});
|
|
686
|
+
```typescript
|
|
687
|
+
import { validateWorkflow } from '@graph-compose/client';
|
|
393
688
|
|
|
394
|
-
|
|
395
|
-
graph.withWebhookUrl("https://my-app.com/graph-compose-webhook");
|
|
689
|
+
const result = validateWorkflow(workflow);
|
|
396
690
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
// workflowTaskTimeout: "1m" // Max time for a single step/task
|
|
401
|
-
});
|
|
691
|
+
if (!result.isValid) {
|
|
692
|
+
console.error('Validation failed:', result.errors);
|
|
693
|
+
}
|
|
402
694
|
```
|
|
403
695
|
|
|
404
|
-
##
|
|
696
|
+
## Executing Workflows
|
|
405
697
|
|
|
406
|
-
|
|
698
|
+
### Local Validation (Client-Side)
|
|
407
699
|
|
|
408
|
-
|
|
700
|
+
Validate workflow structure without making API calls:
|
|
409
701
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
702
|
+
```typescript
|
|
703
|
+
const workflow = new GraphCompose({ token: 'your-token' })
|
|
704
|
+
.http('fetchData')
|
|
705
|
+
.get('https://api.example.com/data')
|
|
706
|
+
.end()
|
|
707
|
+
.build();
|
|
416
708
|
|
|
417
|
-
|
|
709
|
+
// Client-side validation (fast, no API call)
|
|
710
|
+
const validation = workflow.validate();
|
|
418
711
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const validationResult = graph.validate();
|
|
422
|
-
if (!validationResult.isValid) {
|
|
423
|
-
console.error("Workflow validation failed locally:", validationResult.errors);
|
|
712
|
+
if (!validation.isValid) {
|
|
713
|
+
console.error('Validation errors:', validation.errors);
|
|
424
714
|
} else {
|
|
425
|
-
console.log(
|
|
715
|
+
console.log('Workflow is valid!');
|
|
426
716
|
}
|
|
427
717
|
```
|
|
428
718
|
|
|
429
|
-
###
|
|
719
|
+
### Server-Side Validation
|
|
430
720
|
|
|
431
|
-
|
|
721
|
+
Validate against the GraphCompose API (includes tier limits and quota checks):
|
|
432
722
|
|
|
433
723
|
```typescript
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const apiResponse = await graph.validateApi();
|
|
724
|
+
const workflow = new GraphCompose({ token: 'your-token' })
|
|
725
|
+
.http('fetchData')
|
|
726
|
+
.get('https://api.example.com/data')
|
|
727
|
+
.end();
|
|
439
728
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
} else {
|
|
448
|
-
// Handle cases where the API call itself failed (e.g., network error, auth error)
|
|
449
|
-
console.error(`API validation request failed: ${apiResponse.message}`);
|
|
450
|
-
}
|
|
451
|
-
} catch (error) {
|
|
452
|
-
console.error("Failed to call validation API:", error);
|
|
729
|
+
try {
|
|
730
|
+
const apiResult = await workflow.validateApi();
|
|
731
|
+
|
|
732
|
+
if (apiResult.success && apiResult.data?.isValid) {
|
|
733
|
+
console.log('✅ Workflow passed server validation');
|
|
734
|
+
} else {
|
|
735
|
+
console.error('❌ Validation failed:', apiResult.data?.errors);
|
|
453
736
|
}
|
|
737
|
+
} catch (error) {
|
|
738
|
+
console.error('API error:', error);
|
|
454
739
|
}
|
|
455
|
-
validateWithApi();
|
|
456
740
|
```
|
|
457
741
|
|
|
458
|
-
|
|
742
|
+
**Endpoint:** `POST /workflows/validate`
|
|
459
743
|
|
|
460
|
-
|
|
744
|
+
### Async Execution
|
|
461
745
|
|
|
462
|
-
|
|
746
|
+
Start workflow execution and return immediately (recommended for long-running workflows):
|
|
463
747
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
const apiResponse = await graph.execute({ context: { batchId: "batch-789" } });
|
|
748
|
+
```typescript
|
|
749
|
+
const workflow = new GraphCompose({ token: 'your-token' })
|
|
750
|
+
.http('processData')
|
|
751
|
+
.post('https://api.example.com/process')
|
|
752
|
+
.withBody({ data: 'example' })
|
|
753
|
+
.end();
|
|
471
754
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
console.error("Failed to start async workflow:", error);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
startWorkflowAsync();
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
* **Synchronous (`executeSync`)**: This method starts the workflow and *waits* for it to complete (or fail) before returning the final result or error. This is simpler for short-running workflows but can time out for longer processes.
|
|
487
|
-
|
|
488
|
-
```typescript
|
|
489
|
-
// Assuming all node/tool builders have called .end()
|
|
490
|
-
async function runWorkflowAndWait() {
|
|
491
|
-
try {
|
|
492
|
-
// Pass any runtime context needed
|
|
493
|
-
// executeSync returns ApiResponse<WorkflowResponse | null>
|
|
494
|
-
const apiResponse = await graph.executeSync({ context: { overrideUserId: "new-user-456" } });
|
|
495
|
-
|
|
496
|
-
if (apiResponse.success && apiResponse.data) {
|
|
497
|
-
console.log("Workflow completed synchronously:", apiResponse.data);
|
|
498
|
-
// apiResponse.data contains status, executionState, error etc.
|
|
499
|
-
} else {
|
|
500
|
-
console.error(`Synchronous workflow execution failed: ${apiResponse.message}`);
|
|
501
|
-
}
|
|
502
|
-
} catch (error) {
|
|
503
|
-
console.error("Synchronous workflow execution failed:", error);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
runWorkflowAndWait();
|
|
507
|
-
```
|
|
755
|
+
try {
|
|
756
|
+
const result = await workflow.execute({
|
|
757
|
+
context: {
|
|
758
|
+
userId: '123',
|
|
759
|
+
requestId: 'req-456'
|
|
760
|
+
},
|
|
761
|
+
webhookUrl: 'https://your-app.com/webhook' // Optional: receive completion notification
|
|
762
|
+
});
|
|
508
763
|
|
|
509
|
-
|
|
764
|
+
if (result.success && result.data) {
|
|
765
|
+
console.log('Workflow started!');
|
|
766
|
+
console.log('Workflow ID:', result.data.workflowId);
|
|
767
|
+
console.log('Run ID:', result.data.runId);
|
|
768
|
+
console.log('Status:', result.data.status); // "RUNNING"
|
|
769
|
+
|
|
770
|
+
// Poll for status or wait for webhook
|
|
771
|
+
}
|
|
772
|
+
} catch (error) {
|
|
773
|
+
console.error('Execution failed:', error);
|
|
774
|
+
}
|
|
775
|
+
```
|
|
510
776
|
|
|
511
|
-
|
|
777
|
+
**Endpoint:** `POST /workflows`
|
|
512
778
|
|
|
513
|
-
|
|
514
|
-
* `graph.toJSON()`: An alias for `getWorkflow()`, providing the standard JavaScript method for serialization.
|
|
779
|
+
### Check Workflow Status
|
|
515
780
|
|
|
516
|
-
|
|
781
|
+
Poll for workflow status after async execution:
|
|
517
782
|
|
|
518
783
|
```typescript
|
|
519
|
-
|
|
784
|
+
const workflowId = 'wf_abc123';
|
|
785
|
+
|
|
786
|
+
try {
|
|
787
|
+
const statusResult = await workflow.getWorkflowStatus(workflowId);
|
|
788
|
+
|
|
789
|
+
if (statusResult.success && statusResult.data) {
|
|
790
|
+
console.log('Status:', statusResult.data.status);
|
|
791
|
+
// "RUNNING" | "COMPLETED" | "FAILED" | "TERMINATED"
|
|
792
|
+
|
|
793
|
+
console.log('Started at:', statusResult.data.startTime);
|
|
794
|
+
|
|
795
|
+
if (statusResult.data.status === 'COMPLETED') {
|
|
796
|
+
console.log('Results:', statusResult.data.results);
|
|
797
|
+
} else if (statusResult.data.status === 'FAILED') {
|
|
798
|
+
console.log('Error:', statusResult.data.error);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
} catch (error) {
|
|
802
|
+
console.error('Failed to get status:', error);
|
|
803
|
+
}
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
**Endpoint:** `GET /workflows/{workflowId}`
|
|
520
807
|
|
|
521
|
-
|
|
522
|
-
console.log("Raw Workflow Definition Object:", workflowDefinition);
|
|
808
|
+
### Terminate Running Workflow
|
|
523
809
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
810
|
+
Stop a workflow that's currently executing:
|
|
811
|
+
|
|
812
|
+
```typescript
|
|
813
|
+
const workflowId = 'wf_abc123';
|
|
814
|
+
|
|
815
|
+
try {
|
|
816
|
+
const result = await workflow.terminateWorkflow(workflowId, {
|
|
817
|
+
runId: 'run_xyz789', // Optional: specific run ID
|
|
818
|
+
reason: 'User requested cancellation' // Optional
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
if (result.success) {
|
|
822
|
+
console.log('Workflow terminated successfully');
|
|
823
|
+
}
|
|
824
|
+
} catch (error) {
|
|
825
|
+
console.error('Failed to terminate:', error);
|
|
826
|
+
}
|
|
527
827
|
```
|
|
528
828
|
|
|
529
|
-
|
|
829
|
+
**Endpoint:** `POST /workflows/{workflowId}/terminate`
|
|
530
830
|
|
|
531
|
-
|
|
831
|
+
### Webhook Notifications
|
|
532
832
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
try {
|
|
553
|
-
const terminateResult = await graph.terminateWorkflow(workflowId, { reason });
|
|
554
|
-
console.log(`Termination request for ${workflowId}:`, terminateResult);
|
|
555
|
-
} catch (error) {
|
|
556
|
-
console.error("Failed to terminate workflow:", error);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
stopWorkflow("running-workflow-id-456", "User cancelled operation");
|
|
560
|
-
```
|
|
833
|
+
Receive notifications when async workflows complete:
|
|
834
|
+
|
|
835
|
+
```typescript
|
|
836
|
+
const workflow = new GraphCompose({ token: 'your-token' })
|
|
837
|
+
.http('longProcess')
|
|
838
|
+
.post('https://api.example.com/long-process')
|
|
839
|
+
.end()
|
|
840
|
+
.withWebhookUrl('https://your-app.com/webhook');
|
|
841
|
+
|
|
842
|
+
// Your webhook will receive:
|
|
843
|
+
// POST https://your-app.com/webhook
|
|
844
|
+
// {
|
|
845
|
+
// "workflowId": "wf_abc123",
|
|
846
|
+
// "runId": "run_xyz789",
|
|
847
|
+
// "status": "COMPLETED",
|
|
848
|
+
// "results": { ... },
|
|
849
|
+
// "completedAt": "2024-01-15T10:30:00Z"
|
|
850
|
+
// }
|
|
851
|
+
```
|
|
561
852
|
|
|
562
|
-
|
|
853
|
+
### API Response Format
|
|
563
854
|
|
|
564
|
-
|
|
855
|
+
All API methods return a consistent response structure:
|
|
565
856
|
|
|
566
857
|
```typescript
|
|
567
|
-
|
|
858
|
+
interface ApiResponse<T> {
|
|
859
|
+
success: boolean;
|
|
860
|
+
data: T | null;
|
|
861
|
+
error?: {
|
|
862
|
+
code: string;
|
|
863
|
+
message: string;
|
|
864
|
+
details?: any;
|
|
865
|
+
};
|
|
866
|
+
metadata?: {
|
|
867
|
+
requestId: string;
|
|
868
|
+
timestamp: string;
|
|
869
|
+
version: string;
|
|
870
|
+
};
|
|
871
|
+
}
|
|
568
872
|
|
|
569
|
-
|
|
570
|
-
|
|
873
|
+
// Example: Successful execution
|
|
874
|
+
{
|
|
875
|
+
"success": true,
|
|
876
|
+
"data": {
|
|
877
|
+
"workflowId": "wf_abc123",
|
|
878
|
+
"runId": "run_xyz789",
|
|
879
|
+
"status": "RUNNING",
|
|
880
|
+
"startTime": "2024-01-15T10:00:00Z"
|
|
881
|
+
},
|
|
882
|
+
"metadata": {
|
|
883
|
+
"requestId": "req_456",
|
|
884
|
+
"timestamp": "2024-01-15T10:00:00Z",
|
|
885
|
+
"version": "v1"
|
|
886
|
+
}
|
|
887
|
+
}
|
|
571
888
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
889
|
+
// Example: Error response
|
|
890
|
+
{
|
|
891
|
+
"success": false,
|
|
892
|
+
"data": null,
|
|
893
|
+
"error": {
|
|
894
|
+
"code": "VALIDATION_ERROR",
|
|
895
|
+
"message": "Invalid node configuration",
|
|
896
|
+
"details": {
|
|
897
|
+
"nodeId": "fetchData",
|
|
898
|
+
"issue": "Missing required field: url"
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
### Workflow Status Types
|
|
577
905
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
906
|
+
```typescript
|
|
907
|
+
type WorkflowStatus =
|
|
908
|
+
| "RUNNING" // Workflow is currently executing
|
|
909
|
+
| "COMPLETED" // Workflow finished successfully
|
|
910
|
+
| "FAILED" // Workflow encountered an error
|
|
911
|
+
| "TERMINATED"; // Workflow was manually stopped
|
|
912
|
+
```
|
|
585
913
|
|
|
586
|
-
|
|
587
|
-
|
|
914
|
+
### Complete Execution Example
|
|
915
|
+
|
|
916
|
+
```typescript
|
|
917
|
+
import { GraphCompose } from '@graph-compose/client';
|
|
588
918
|
|
|
589
|
-
|
|
590
|
-
//
|
|
591
|
-
const
|
|
919
|
+
async function runWorkflow() {
|
|
920
|
+
// Build the workflow
|
|
921
|
+
const workflow = new GraphCompose({ token: process.env.GRAPH_COMPOSE_TOKEN })
|
|
922
|
+
.http('fetchUser')
|
|
923
|
+
.get('https://api.example.com/users/{{context.userId}}')
|
|
924
|
+
.end()
|
|
925
|
+
.http('enrichProfile')
|
|
926
|
+
.post('https://api.example.com/enrich')
|
|
927
|
+
.withBody({
|
|
928
|
+
userId: '{{results.fetchUser.id}}',
|
|
929
|
+
email: '{{results.fetchUser.email}}'
|
|
930
|
+
})
|
|
931
|
+
.dependsOn(['fetchUser'])
|
|
932
|
+
.end();
|
|
933
|
+
|
|
934
|
+
// Validate locally
|
|
935
|
+
const validation = workflow.validate();
|
|
592
936
|
if (!validation.isValid) {
|
|
593
|
-
|
|
594
|
-
return;
|
|
937
|
+
throw new Error(`Invalid workflow: ${validation.errors.map(e => e.message).join(', ')}`);
|
|
595
938
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
939
|
+
|
|
940
|
+
// Execute async
|
|
941
|
+
const execution = await workflow.execute({
|
|
942
|
+
context: { userId: '123' },
|
|
943
|
+
webhookUrl: 'https://myapp.com/webhook'
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
if (!execution.success || !execution.data) {
|
|
947
|
+
throw new Error('Execution failed');
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
const workflowId = execution.data.workflowId;
|
|
951
|
+
console.log('Workflow started:', workflowId);
|
|
952
|
+
|
|
953
|
+
// Poll for completion
|
|
954
|
+
let status = 'RUNNING';
|
|
955
|
+
while (status === 'RUNNING') {
|
|
956
|
+
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2s
|
|
957
|
+
|
|
958
|
+
const statusResult = await workflow.getWorkflowStatus(workflowId);
|
|
959
|
+
if (statusResult.success && statusResult.data) {
|
|
960
|
+
status = statusResult.data.status;
|
|
961
|
+
console.log('Current status:', status);
|
|
614
962
|
}
|
|
615
|
-
} catch (error) {
|
|
616
|
-
console.error("Execution Failed:", error);
|
|
617
963
|
}
|
|
618
|
-
}
|
|
619
964
|
|
|
620
|
-
//
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
console.log('[Status Check] Results:', statusResult.results);
|
|
629
|
-
} else if (statusResult.status === 'FAILED') {
|
|
630
|
-
console.error('[Status Check] Error:', statusResult.error);
|
|
965
|
+
// Get final results
|
|
966
|
+
const finalResult = await workflow.getWorkflowStatus(workflowId);
|
|
967
|
+
if (finalResult.success && finalResult.data) {
|
|
968
|
+
if (finalResult.data.status === 'COMPLETED') {
|
|
969
|
+
console.log('✅ Workflow completed successfully!');
|
|
970
|
+
console.log('Results:', finalResult.data.results);
|
|
971
|
+
} else {
|
|
972
|
+
console.error('❌ Workflow failed:', finalResult.data.error);
|
|
631
973
|
}
|
|
632
|
-
} catch (error) {
|
|
633
|
-
console.error("[Status Check] Failed to get workflow status:", error);
|
|
634
974
|
}
|
|
635
975
|
}
|
|
636
976
|
|
|
637
|
-
|
|
977
|
+
runWorkflow().catch(console.error);
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
## API Reference
|
|
981
|
+
|
|
982
|
+
### GraphCompose
|
|
983
|
+
|
|
984
|
+
Main workflow builder class.
|
|
985
|
+
|
|
986
|
+
#### Constructor
|
|
987
|
+
|
|
988
|
+
```typescript
|
|
989
|
+
new GraphCompose(options?: { token?: string })
|
|
638
990
|
```
|
|
639
991
|
|
|
992
|
+
- `options.token` - API token (or set `GRAPH_COMPOSE_TOKEN` env var)
|
|
993
|
+
|
|
994
|
+
#### Building Methods
|
|
995
|
+
|
|
996
|
+
- `http(id: string): NodeBuilder` - Create HTTP node
|
|
997
|
+
- `adk(id: string): AdkNodeBuilder` - Create ADK agent node
|
|
998
|
+
- `errorBoundary(id: string): ErrorBoundaryBuilder` - Create error boundary
|
|
999
|
+
- `build(): WorkflowGraph` - Build the workflow definition
|
|
1000
|
+
|
|
1001
|
+
#### Configuration Methods
|
|
1002
|
+
|
|
1003
|
+
- `withContext(context: Record<string, any>): GraphCompose` - Set workflow context
|
|
1004
|
+
- `withWebhookUrl(url: string): GraphCompose` - Set webhook for async notifications
|
|
1005
|
+
- `withWorkflowConfig(config: WorkflowConfig): GraphCompose` - Set workflow-level config
|
|
1006
|
+
|
|
1007
|
+
#### Validation Methods
|
|
1008
|
+
|
|
1009
|
+
- `validate(): ValidationResult` - Client-side validation
|
|
1010
|
+
- `validateApi(): Promise<ApiResponse>` - Server-side validation with tier checks
|
|
1011
|
+
|
|
1012
|
+
#### Execution Methods
|
|
1013
|
+
|
|
1014
|
+
- `execute(options?): Promise<ApiResponse>` - Execute workflow (returns immediately with workflow ID)
|
|
1015
|
+
- `getWorkflowStatus(workflowId: string): Promise<ApiResponse>` - Check workflow status
|
|
1016
|
+
- `terminateWorkflow(workflowId: string, options?): Promise<ApiResponse>` - Stop workflow
|
|
1017
|
+
|
|
1018
|
+
### NodeBuilder
|
|
1019
|
+
|
|
1020
|
+
Builder for HTTP nodes.
|
|
1021
|
+
|
|
1022
|
+
#### HTTP Methods
|
|
1023
|
+
- `get(url: string)` - GET request
|
|
1024
|
+
- `post(url: string)` - POST request
|
|
1025
|
+
- `put(url: string)` - PUT request
|
|
1026
|
+
- `patch(url: string)` - PATCH request
|
|
1027
|
+
- `delete(url: string)` - DELETE request
|
|
1028
|
+
|
|
1029
|
+
#### Configuration
|
|
1030
|
+
- `withHeaders(headers: Record<string, string>)` - Set headers
|
|
1031
|
+
- `withBody(body: any)` - Set request body
|
|
1032
|
+
- `withParams(params: Record<string, string>)` - Set URL params
|
|
1033
|
+
- `withRetries(policy: RetryPolicy)` - Configure retry policy
|
|
1034
|
+
- `withStartToCloseTimeout(timeout: string)` - Set activity timeout
|
|
1035
|
+
- `dependsOn(nodeIds: string[])` - Set dependencies
|
|
1036
|
+
- `runWhen(condition: Condition)` - Conditional execution
|
|
1037
|
+
|
|
1038
|
+
#### Completion
|
|
1039
|
+
- `end(): GraphCompose` - Finalize node
|
|
1040
|
+
|
|
1041
|
+
### AdkNodeBuilder
|
|
1042
|
+
|
|
1043
|
+
Builder for ADK agent nodes.
|
|
1044
|
+
|
|
1045
|
+
#### Methods
|
|
1046
|
+
- `withWorkflow(workflow: ADKWorkflowDefinition)` - Set agent workflow
|
|
1047
|
+
- `withInitialPrompt(prompt: string)` - Set initial prompt
|
|
1048
|
+
- `withMaxCycles(max: number)` - Set max orchestration cycles
|
|
1049
|
+
- `withState(state: Record<string, any>)` - Seed initial state
|
|
1050
|
+
- `withRetries(policy: RetryPolicy)` - Configure retry policy
|
|
1051
|
+
- `dependsOn(nodeIds: string[])` - Set dependencies
|
|
1052
|
+
- `end(): GraphCompose` - Finalize node
|
|
1053
|
+
|
|
1054
|
+
### AdkWorkflowBuilder
|
|
1055
|
+
|
|
1056
|
+
Builder for agent workflow definitions.
|
|
1057
|
+
|
|
1058
|
+
#### Methods
|
|
1059
|
+
- `agent(config: AgentConfig)` - Add agent
|
|
1060
|
+
- `httpTool(config: HttpToolConfig)` - Add HTTP tool
|
|
1061
|
+
- `agentTool(config: AgentToolConfig)` - Add agent tool
|
|
1062
|
+
- `setRootAgent(agentId: string)` - Set entry point agent
|
|
1063
|
+
- `withMaxCycles(max: number)` - Set workflow-level max cycles
|
|
1064
|
+
- `build(): ADKWorkflowDefinition` - Build the workflow
|
|
1065
|
+
|
|
1066
|
+
### Helper Functions
|
|
1067
|
+
|
|
1068
|
+
#### Agent Helpers
|
|
1069
|
+
- `createLlmAgent(config)` - Create LLM agent config
|
|
1070
|
+
- `createSequentialAgent(config)` - Create sequential orchestrator
|
|
1071
|
+
- `createParallelAgent(config)` - Create parallel orchestrator
|
|
1072
|
+
- `createLoopAgent(config)` - Create loop orchestrator
|
|
1073
|
+
- `createSubAgentReference(agentId)` - Create agent reference
|
|
1074
|
+
|
|
1075
|
+
#### Tool Helpers
|
|
1076
|
+
- `createHttpTool(config)` - Create HTTP tool config
|
|
1077
|
+
- `createAgentTool(config)` - Create agent tool config
|
|
1078
|
+
|
|
1079
|
+
## TypeScript Types
|
|
1080
|
+
|
|
1081
|
+
Full TypeScript support with exported types:
|
|
1082
|
+
|
|
1083
|
+
```typescript
|
|
1084
|
+
import type {
|
|
1085
|
+
WorkflowGraph,
|
|
1086
|
+
HttpNode,
|
|
1087
|
+
AdkNode,
|
|
1088
|
+
RetryPolicy,
|
|
1089
|
+
ADKWorkflowDefinition,
|
|
1090
|
+
AgentConfig,
|
|
1091
|
+
ToolConfig,
|
|
1092
|
+
ValidationResult
|
|
1093
|
+
} from '@graph-compose/client';
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
## Examples
|
|
1097
|
+
|
|
1098
|
+
See the `examples/` directory for complete working examples:
|
|
1099
|
+
|
|
1100
|
+
- **HTTP Workflow**: Service orchestration
|
|
1101
|
+
- **ADK Basic**: Simple agent with tools
|
|
1102
|
+
- **ADK Advanced**: Multi-agent hierarchies
|
|
1103
|
+
- **Error Handling**: Retry and fallback patterns
|
|
1104
|
+
- **Conditional**: Branch-based workflows
|
|
1105
|
+
|
|
640
1106
|
## Contributing
|
|
641
1107
|
|
|
642
|
-
Contributions are welcome! Please
|
|
1108
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines.
|
|
643
1109
|
|
|
644
1110
|
## License
|
|
645
1111
|
|
|
646
|
-
|
|
1112
|
+
MIT License - see [LICENSE](../../LICENSE) for details.
|
|
1113
|
+
|
|
1114
|
+
## Support
|
|
1115
|
+
|
|
1116
|
+
- 📖 [Documentation](https://github.com/your-org/graph-compose)
|
|
1117
|
+
- 💬 [Discord Community](https://discord.gg/your-server)
|
|
1118
|
+
- 🐛 [Issue Tracker](https://github.com/your-org/graph-compose/issues)
|
|
1119
|
+
- 📧 [Email Support](mailto:support@example.com)
|