@shipfox/workflow-document 3.1.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/document/index.d.ts +1 -1
- package/dist/document/index.d.ts.map +1 -1
- package/dist/document/index.js +1 -1
- package/dist/document/index.js.map +1 -1
- package/dist/document/workflow-document-parser.d.ts +2 -2
- package/dist/document/workflow-document-parser.d.ts.map +1 -1
- package/dist/document/workflow-document-parser.js +18 -5
- package/dist/document/workflow-document-parser.js.map +1 -1
- package/dist/document/workflow-document.d.ts +324 -14
- package/dist/document/workflow-document.d.ts.map +1 -1
- package/dist/document/workflow-document.js +408 -21
- package/dist/document/workflow-document.js.map +1 -1
- package/dist/document/workflow-json-schema.d.ts.map +1 -1
- package/dist/document/workflow-json-schema.js +16 -1
- package/dist/document/workflow-json-schema.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -12,6 +12,11 @@ export const WORKFLOW_LITERAL_NAME_PATTERN = /^(?:[^$]|\$\$\{\{|\$(?!\{\{))*$/;
|
|
|
12
12
|
// The inverse of a literal name: a literal prefix followed by an unescaped
|
|
13
13
|
// `${{`. An enum field that also accepts a template matches one or the other.
|
|
14
14
|
export const WORKFLOW_INTERPOLATED_VALUE_PATTERN = /^(?:[^$]|\$\$\{\{|\$(?!\{\{))*\$\{\{/;
|
|
15
|
+
export const WORKFLOW_SESSION_KEY_MAX_LENGTH = 128;
|
|
16
|
+
export const WORKFLOW_SESSION_KEY_PATTERN_SOURCE = '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$';
|
|
17
|
+
export const WORKFLOW_SESSION_KEY_PATTERN = new RegExp(WORKFLOW_SESSION_KEY_PATTERN_SOURCE);
|
|
18
|
+
const workflowSessionKeyLiteralPartPattern = /^[A-Za-z0-9._-]*$/;
|
|
19
|
+
const workflowSessionKeyLiteralPartStartPattern = /^[A-Za-z0-9]/;
|
|
15
20
|
// Reasoning effort is an enum so editors can complete it, and a template so a
|
|
16
21
|
// workflow can choose the effort from run context. The resolved value is
|
|
17
22
|
// checked against the harness levels when the step is dispatched.
|
|
@@ -52,6 +57,8 @@ export const WORKFLOW_DOCUMENT_JOB_OUTPUTS_MAX_ENTRIES = WORKFLOW_DOCUMENT_ENV_M
|
|
|
52
57
|
export const WORKFLOW_DOCUMENT_STEP_OUTPUTS_MAX_ENTRIES = WORKFLOW_DOCUMENT_ENV_MAX_ENTRIES;
|
|
53
58
|
export const WORKFLOW_DOCUMENT_STEP_OUTPUT_SCHEMA_MAX_SERIALIZED_BYTES = WORKFLOW_DOCUMENT_ENV_MAX_SERIALIZED_BYTES;
|
|
54
59
|
export const WORKFLOW_DOCUMENT_STEP_OUTPUT_SCHEMA_MAX_DEPTH = 64;
|
|
60
|
+
export const WORKFLOW_DOCUMENT_TOOL_WITH_MAX_SERIALIZED_BYTES = 32 * 1024;
|
|
61
|
+
export const WORKFLOW_DOCUMENT_TOOL_WITH_MAX_DEPTH = 16;
|
|
55
62
|
const utf8Encoder = new TextEncoder();
|
|
56
63
|
export const workflowDocumentEnvSchema = z.record(envNameSchema, z.union([
|
|
57
64
|
envStringValueSchema,
|
|
@@ -76,10 +83,186 @@ export const workflowDocumentEnvSchema = z.record(envNameSchema, z.union([
|
|
|
76
83
|
description: `Environment variables as string, number, or boolean values. Each map allows up to ${WORKFLOW_DOCUMENT_ENV_MAX_ENTRIES} entries and ${WORKFLOW_DOCUMENT_ENV_MAX_SERIALIZED_BYTES} serialized bytes.`
|
|
77
84
|
});
|
|
78
85
|
const workflowDocumentStepOutputKeyPattern = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
86
|
+
export const workflowDocumentToolStepWithSchema = z// Validate nested values in an iterative refinement. A recursive Zod schema
|
|
87
|
+
// would traverse hostile depth before the tool-input limits can reject it.
|
|
88
|
+
.record(z.string().min(1), z.unknown()).superRefine((withValue, ctx)=>{
|
|
89
|
+
// The server injects `method` for `family.method` tools, so the author can
|
|
90
|
+
// never set it.
|
|
91
|
+
if ('method' in withValue) {
|
|
92
|
+
ctx.addIssue({
|
|
93
|
+
code: 'custom',
|
|
94
|
+
path: [
|
|
95
|
+
'method'
|
|
96
|
+
],
|
|
97
|
+
message: '`method` is not a valid tool input; the server injects it for `family.method` tools.'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
validateWorkflowDocumentToolWith(withValue, ctx);
|
|
101
|
+
}).transform((withValue)=>withValue).meta({
|
|
102
|
+
description: 'Tool inputs as a JSON tree. The map allows up to ' + WORKFLOW_DOCUMENT_TOOL_WITH_MAX_SERIALIZED_BYTES + ' serialized bytes and ' + WORKFLOW_DOCUMENT_TOOL_WITH_MAX_DEPTH + ' nesting levels. Tool steps are not available yet.'
|
|
103
|
+
});
|
|
104
|
+
function validateWorkflowDocumentToolWith(withValue, ctx) {
|
|
105
|
+
const activeObjects = new Set();
|
|
106
|
+
const tasks = [
|
|
107
|
+
{
|
|
108
|
+
kind: 'value',
|
|
109
|
+
value: withValue,
|
|
110
|
+
depth: 1,
|
|
111
|
+
path: []
|
|
112
|
+
}
|
|
113
|
+
];
|
|
114
|
+
let serializedBytes = 0;
|
|
115
|
+
const addSerializedBytes = (byteLength)=>{
|
|
116
|
+
serializedBytes += byteLength;
|
|
117
|
+
if (serializedBytes <= WORKFLOW_DOCUMENT_TOOL_WITH_MAX_SERIALIZED_BYTES) return true;
|
|
118
|
+
ctx.addIssue({
|
|
119
|
+
code: 'custom',
|
|
120
|
+
message: `Tool \`with\` cannot serialize to more than ${WORKFLOW_DOCUMENT_TOOL_WITH_MAX_SERIALIZED_BYTES} bytes.`
|
|
121
|
+
});
|
|
122
|
+
return false;
|
|
123
|
+
};
|
|
124
|
+
while(tasks.length > 0){
|
|
125
|
+
const task = tasks.pop();
|
|
126
|
+
if (task === undefined) continue;
|
|
127
|
+
if (task.kind === 'bytes') {
|
|
128
|
+
if (!addSerializedBytes(task.byteLength)) return;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (task.kind === 'end') {
|
|
132
|
+
activeObjects.delete(task.value);
|
|
133
|
+
if (!addSerializedBytes(task.byteLength)) return;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const { value, depth, path } = task;
|
|
137
|
+
if (value === null) {
|
|
138
|
+
if (!addSerializedBytes(4)) return;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (typeof value === 'string' || typeof value === 'boolean') {
|
|
142
|
+
if (!addSerializedBytes(jsonPrimitiveByteLength(value))) return;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (typeof value === 'number') {
|
|
146
|
+
if (!Number.isFinite(value)) {
|
|
147
|
+
ctx.addIssue({
|
|
148
|
+
code: 'custom',
|
|
149
|
+
path,
|
|
150
|
+
message: 'Tool `with` values must be JSON-compatible.'
|
|
151
|
+
});
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (!addSerializedBytes(jsonPrimitiveByteLength(value))) return;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (typeof value !== 'object' || value === null) {
|
|
158
|
+
ctx.addIssue({
|
|
159
|
+
code: 'custom',
|
|
160
|
+
path,
|
|
161
|
+
message: 'Tool `with` values must be JSON-compatible.'
|
|
162
|
+
});
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (depth > WORKFLOW_DOCUMENT_TOOL_WITH_MAX_DEPTH) {
|
|
166
|
+
ctx.addIssue({
|
|
167
|
+
code: 'custom',
|
|
168
|
+
message: `Tool \`with\` cannot be nested deeper than ${WORKFLOW_DOCUMENT_TOOL_WITH_MAX_DEPTH} levels.`
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (activeObjects.has(value)) {
|
|
173
|
+
ctx.addIssue({
|
|
174
|
+
code: 'custom',
|
|
175
|
+
path,
|
|
176
|
+
message: 'Tool `with` values must be a JSON tree.'
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
activeObjects.add(value);
|
|
181
|
+
if (Array.isArray(value)) {
|
|
182
|
+
if (!addSerializedBytes(1)) return;
|
|
183
|
+
tasks.push({
|
|
184
|
+
kind: 'end',
|
|
185
|
+
value,
|
|
186
|
+
byteLength: 1
|
|
187
|
+
});
|
|
188
|
+
for(let index = value.length - 1; index >= 0; index -= 1){
|
|
189
|
+
tasks.push({
|
|
190
|
+
kind: 'value',
|
|
191
|
+
value: value[index],
|
|
192
|
+
depth: depth + 1,
|
|
193
|
+
path: [
|
|
194
|
+
...path,
|
|
195
|
+
index
|
|
196
|
+
]
|
|
197
|
+
});
|
|
198
|
+
if (index > 0) tasks.push({
|
|
199
|
+
kind: 'bytes',
|
|
200
|
+
byteLength: 1
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (!isJsonRecord(value)) {
|
|
206
|
+
ctx.addIssue({
|
|
207
|
+
code: 'custom',
|
|
208
|
+
path,
|
|
209
|
+
message: 'Tool `with` values must be a JSON tree.'
|
|
210
|
+
});
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (!addSerializedBytes(1)) return;
|
|
214
|
+
tasks.push({
|
|
215
|
+
kind: 'end',
|
|
216
|
+
value,
|
|
217
|
+
byteLength: 1
|
|
218
|
+
});
|
|
219
|
+
const entries = Object.entries(value);
|
|
220
|
+
for(let index = entries.length - 1; index >= 0; index -= 1){
|
|
221
|
+
const entry = entries[index];
|
|
222
|
+
if (entry === undefined) continue;
|
|
223
|
+
const [key, child] = entry;
|
|
224
|
+
tasks.push({
|
|
225
|
+
kind: 'value',
|
|
226
|
+
value: child,
|
|
227
|
+
depth: depth + 1,
|
|
228
|
+
path: [
|
|
229
|
+
...path,
|
|
230
|
+
key
|
|
231
|
+
]
|
|
232
|
+
});
|
|
233
|
+
tasks.push({
|
|
234
|
+
kind: 'bytes',
|
|
235
|
+
byteLength: jsonPrimitiveByteLength(key) + 1
|
|
236
|
+
});
|
|
237
|
+
if (index > 0) tasks.push({
|
|
238
|
+
kind: 'bytes',
|
|
239
|
+
byteLength: 1
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function jsonPrimitiveByteLength(value) {
|
|
245
|
+
return utf8Encoder.encode(JSON.stringify(value)).byteLength;
|
|
246
|
+
}
|
|
247
|
+
function jsonSerializedByteLength(value) {
|
|
248
|
+
try {
|
|
249
|
+
const serialized = JSON.stringify(value);
|
|
250
|
+
return serialized === undefined ? undefined : utf8Encoder.encode(serialized).byteLength;
|
|
251
|
+
} catch {
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function isJsonRecord(value) {
|
|
256
|
+
const prototype = Object.getPrototypeOf(value);
|
|
257
|
+
return prototype === Object.prototype || prototype === null;
|
|
258
|
+
}
|
|
79
259
|
const workflowDocumentStepOutputTypeSchema = z.enum(workflowDocumentStepOutputTypes).meta({
|
|
80
260
|
description: 'Declared output type. Use `json` when the output has a JSON Schema.'
|
|
81
261
|
});
|
|
82
|
-
const
|
|
262
|
+
const workflowDocumentToolStepOutputMappingValueSchema = z.string().min(1).refine((value)=>value.includes('$' + '{{'), {
|
|
263
|
+
message: 'Tool-step output mappings must use a $' + '{{ }} expression.'
|
|
264
|
+
});
|
|
265
|
+
export const workflowDocumentStepOutputDeclarationSchema = z.union([
|
|
83
266
|
workflowDocumentStepOutputTypeSchema.transform((type)=>({
|
|
84
267
|
type
|
|
85
268
|
})),
|
|
@@ -112,7 +295,17 @@ const workflowDocumentStepOutputDeclarationSchema = z.union([
|
|
|
112
295
|
});
|
|
113
296
|
return;
|
|
114
297
|
}
|
|
115
|
-
const serializedBytes =
|
|
298
|
+
const serializedBytes = jsonSerializedByteLength(schema);
|
|
299
|
+
if (serializedBytes === undefined) {
|
|
300
|
+
ctx.addIssue({
|
|
301
|
+
code: 'custom',
|
|
302
|
+
path: [
|
|
303
|
+
'schema'
|
|
304
|
+
],
|
|
305
|
+
message: 'Schema must be a serializable JSON Schema document.'
|
|
306
|
+
});
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
116
309
|
if (serializedBytes > WORKFLOW_DOCUMENT_STEP_OUTPUT_SCHEMA_MAX_SERIALIZED_BYTES) {
|
|
117
310
|
ctx.addIssue({
|
|
118
311
|
code: 'custom',
|
|
@@ -133,7 +326,12 @@ const workflowDocumentStepOutputDeclarationSchema = z.union([
|
|
|
133
326
|
});
|
|
134
327
|
}
|
|
135
328
|
});
|
|
136
|
-
|
|
329
|
+
function stepOutputsAreMappingForm(outputs) {
|
|
330
|
+
const interpolationOpen = '$' + '{{';
|
|
331
|
+
const values = Object.values(outputs);
|
|
332
|
+
return values.some((value)=>typeof value === 'string' && value.includes(interpolationOpen));
|
|
333
|
+
}
|
|
334
|
+
function stepOutputsRecordChecks(outputs, ctx) {
|
|
137
335
|
const entries = Object.keys(outputs).length;
|
|
138
336
|
if (entries > WORKFLOW_DOCUMENT_STEP_OUTPUTS_MAX_ENTRIES) {
|
|
139
337
|
ctx.addIssue({
|
|
@@ -151,21 +349,38 @@ export const workflowDocumentStepOutputsSchema = z.record(z.string(), workflowDo
|
|
|
151
349
|
message: 'Output keys must be CEL identifiers.'
|
|
152
350
|
});
|
|
153
351
|
}
|
|
154
|
-
}
|
|
352
|
+
}
|
|
353
|
+
export const workflowDocumentStepOutputsSchema = z.record(z.string(), workflowDocumentStepOutputDeclarationSchema).superRefine((outputs, ctx)=>stepOutputsRecordChecks(outputs, ctx)).meta({
|
|
155
354
|
description: `Named step outputs. Keys must be CEL identifiers and each step allows up to ${WORKFLOW_DOCUMENT_STEP_OUTPUTS_MAX_ENTRIES} declarations.`
|
|
156
355
|
});
|
|
356
|
+
// The tool-step `outputs` form maps output keys to a single `${{ }}` expression
|
|
357
|
+
// over `result`. The expression layer validates the interpolation; the mapping
|
|
358
|
+
// form is rejected with the reserved tool step fields until tool steps exist.
|
|
359
|
+
export const workflowDocumentToolStepOutputsSchema = z.record(z.string(), workflowDocumentToolStepOutputMappingValueSchema).superRefine((outputs, ctx)=>stepOutputsRecordChecks(outputs, ctx)).meta({
|
|
360
|
+
description: 'Tool-step output mappings over `result`. Each value is exactly one $' + '{{ }} expression. Tool steps are not available yet.'
|
|
361
|
+
});
|
|
362
|
+
// `outputs` carries the declaration form on run, agent, and checkout steps and
|
|
363
|
+
// the expression mapping form on tool steps. One value union accepts both so a
|
|
364
|
+
// reserved tool step parses and is rejected by the step `superRefine`; zod
|
|
365
|
+
// reports the declaration branch's own issues for malformed declarations, and
|
|
366
|
+
// the step `superRefine` rejects the mapping form on every other step kind.
|
|
367
|
+
const workflowDocumentStepOutputValueSchema = z.union([
|
|
368
|
+
workflowDocumentStepOutputDeclarationSchema,
|
|
369
|
+
workflowDocumentToolStepOutputMappingValueSchema
|
|
370
|
+
]);
|
|
371
|
+
const workflowDocumentStepOutputsFieldSchema = z.record(z.string(), workflowDocumentStepOutputValueSchema).superRefine((outputs, ctx)=>stepOutputsRecordChecks(outputs, ctx));
|
|
157
372
|
const workflowDocumentTriggerBaseSchema = {
|
|
158
373
|
source: z.string().min(1).meta({
|
|
159
|
-
description: 'Integration connection slug or built-in trigger source. See [
|
|
374
|
+
description: 'Integration connection slug or built-in trigger source. See [Integrations](/integrations) for provider sources.'
|
|
160
375
|
}),
|
|
161
376
|
with: z.record(z.string(), z.unknown()).optional().meta({
|
|
162
|
-
description: 'Provider-specific values used to match or configure the trigger. See [
|
|
377
|
+
description: 'Provider-specific values used to match or configure the trigger. See the provider event catalog in [Integrations](/integrations).'
|
|
163
378
|
}),
|
|
164
379
|
filter: z.string().min(1).optional().meta({
|
|
165
380
|
description: 'CEL condition that filters matching events. It is not supported for `manual` or `cron` triggers. See [Expressions](/reference/expressions) and [Contexts](/reference/contexts#context-availability).'
|
|
166
381
|
}),
|
|
167
382
|
config: z.record(z.string(), z.unknown()).optional().meta({
|
|
168
|
-
description: 'Source-specific configuration. It is supported only for top-level triggers with a known built-in source. See [
|
|
383
|
+
description: 'Source-specific configuration. It is supported only for top-level triggers with a known built-in source. See [Schedule workflows](/how-to/author-workflows/schedule-workflows).'
|
|
169
384
|
})
|
|
170
385
|
};
|
|
171
386
|
export const triggerSourceConfigSchemas = {
|
|
@@ -353,6 +568,115 @@ export const workflowDocumentStepIntegrationSchema = z.strictObject({
|
|
|
353
568
|
description: 'Allows write-capable integration tools. Omit or set false for read-only access.'
|
|
354
569
|
})
|
|
355
570
|
});
|
|
571
|
+
// A session names an agent conversation that continues across steps of one run.
|
|
572
|
+
// The shorthand string form names the session and resumes it; the long form
|
|
573
|
+
// adds an explicit mode. The key is a field template, evaluated at step
|
|
574
|
+
// dispatch with the same context roots the prompt sees.
|
|
575
|
+
const workflowSessionKeyLiteralSchema = z.string().min(1).max(WORKFLOW_SESSION_KEY_MAX_LENGTH).regex(WORKFLOW_SESSION_KEY_PATTERN, {
|
|
576
|
+
message: 'Literal session keys must start with a letter or number and contain only letters, numbers, dots, underscores, or hyphens.'
|
|
577
|
+
});
|
|
578
|
+
const workflowSessionKeyTemplateSchema = z.string().min(1).regex(WORKFLOW_INTERPOLATED_VALUE_PATTERN, {
|
|
579
|
+
message: 'Session keys with interpolation must use a $' + '{{ }} template.'
|
|
580
|
+
}).refine(isValidWorkflowSessionKeyTemplateLiteralParts, {
|
|
581
|
+
message: 'Literal parts of interpolated session keys may contain only letters, numbers, dots, underscores, or hyphens, and may not exceed 128 characters in total.'
|
|
582
|
+
});
|
|
583
|
+
const workflowSessionKeySchema = z.union([
|
|
584
|
+
workflowSessionKeyLiteralSchema,
|
|
585
|
+
workflowSessionKeyTemplateSchema
|
|
586
|
+
]);
|
|
587
|
+
export const workflowDocumentSessionSchema = z.union([
|
|
588
|
+
workflowSessionKeyLiteralSchema,
|
|
589
|
+
workflowSessionKeyTemplateSchema,
|
|
590
|
+
z.strictObject({
|
|
591
|
+
key: workflowSessionKeySchema.meta({
|
|
592
|
+
description: 'Session key, or a $' + '{{ }} interpolation that resolves to one.'
|
|
593
|
+
}),
|
|
594
|
+
mode: z.enum([
|
|
595
|
+
'resume',
|
|
596
|
+
'fork'
|
|
597
|
+
]).optional().meta({
|
|
598
|
+
description: 'Session mode. `resume` continues the session and writes back; `fork` reads a snapshot and never writes. Defaults to `resume`.'
|
|
599
|
+
})
|
|
600
|
+
})
|
|
601
|
+
]).meta({
|
|
602
|
+
description: 'Named agent session continued across steps of one workflow run. A string names the session and resumes it; an object adds the mode.'
|
|
603
|
+
});
|
|
604
|
+
export function isValidWorkflowSessionKeyTemplateLiteralParts(source) {
|
|
605
|
+
let cursor = 0;
|
|
606
|
+
let expressionSeen = false;
|
|
607
|
+
let literalLength = 0;
|
|
608
|
+
while(true){
|
|
609
|
+
const opener = source.indexOf('${{', cursor);
|
|
610
|
+
const literalEnd = opener === -1 ? source.length : opener;
|
|
611
|
+
const literal = source.slice(cursor, literalEnd);
|
|
612
|
+
literalLength += literal.length;
|
|
613
|
+
if (literalLength > WORKFLOW_SESSION_KEY_MAX_LENGTH || !workflowSessionKeyLiteralPartPattern.test(literal) || !expressionSeen && literal.length > 0 && !workflowSessionKeyLiteralPartStartPattern.test(literal)) {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
if (opener === -1) return expressionSeen;
|
|
617
|
+
const close = findWorkflowSessionKeyTemplateClose(source, opener);
|
|
618
|
+
if (close === -1) return false;
|
|
619
|
+
expressionSeen = true;
|
|
620
|
+
cursor = close + 2;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function findWorkflowSessionKeyTemplateClose(source, openerIndex) {
|
|
624
|
+
let index = openerIndex + 3;
|
|
625
|
+
let depth = 0;
|
|
626
|
+
while(index < source.length){
|
|
627
|
+
const stringEnd = scanWorkflowSessionKeyStringLiteral(source, index);
|
|
628
|
+
if (stringEnd !== null) {
|
|
629
|
+
index = stringEnd;
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (source.startsWith('//', index)) {
|
|
633
|
+
const newline = source.indexOf('\n', index + 2);
|
|
634
|
+
index = newline === -1 ? source.length : newline;
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
if (depth === 0 && source.startsWith('}}', index)) return index;
|
|
638
|
+
const char = source[index];
|
|
639
|
+
if (char === '(' || char === '[' || char === '{') {
|
|
640
|
+
depth += 1;
|
|
641
|
+
} else if ((char === ')' || char === ']' || char === '}') && depth > 0) {
|
|
642
|
+
depth -= 1;
|
|
643
|
+
}
|
|
644
|
+
index += 1;
|
|
645
|
+
}
|
|
646
|
+
return -1;
|
|
647
|
+
}
|
|
648
|
+
function scanWorkflowSessionKeyStringLiteral(source, index) {
|
|
649
|
+
for (const prefix of [
|
|
650
|
+
'r',
|
|
651
|
+
'R',
|
|
652
|
+
'b',
|
|
653
|
+
'B',
|
|
654
|
+
''
|
|
655
|
+
]){
|
|
656
|
+
if (!source.startsWith(prefix, index)) continue;
|
|
657
|
+
const quoteIndex = index + prefix.length;
|
|
658
|
+
const quote = source[quoteIndex];
|
|
659
|
+
if (quote !== '"' && quote !== "'") continue;
|
|
660
|
+
const tripleQuote = quote.repeat(3);
|
|
661
|
+
if (source.startsWith(tripleQuote, quoteIndex)) {
|
|
662
|
+
return scanWorkflowSessionKeyQuotedString(source, quoteIndex + 3, tripleQuote, prefix === 'r' || prefix === 'R');
|
|
663
|
+
}
|
|
664
|
+
return scanWorkflowSessionKeyQuotedString(source, quoteIndex + 1, quote, prefix === 'r' || prefix === 'R');
|
|
665
|
+
}
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
function scanWorkflowSessionKeyQuotedString(source, startIndex, delimiter, raw) {
|
|
669
|
+
let index = startIndex;
|
|
670
|
+
while(index < source.length){
|
|
671
|
+
if (!raw && source[index] === '\\') {
|
|
672
|
+
index += 2;
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
if (source.startsWith(delimiter, index)) return index + delimiter.length;
|
|
676
|
+
index += 1;
|
|
677
|
+
}
|
|
678
|
+
return source.length;
|
|
679
|
+
}
|
|
356
680
|
export const workflowDocumentAgentStepFields = [
|
|
357
681
|
'model',
|
|
358
682
|
'prompt',
|
|
@@ -360,7 +684,8 @@ export const workflowDocumentAgentStepFields = [
|
|
|
360
684
|
'thinking',
|
|
361
685
|
'provider',
|
|
362
686
|
'tools',
|
|
363
|
-
'integrations'
|
|
687
|
+
'integrations',
|
|
688
|
+
'session'
|
|
364
689
|
];
|
|
365
690
|
// A step is a run step (`run`), an inline agent step (`prompt`), or a checkout
|
|
366
691
|
// step (`checkout`), never two kinds at once. They share one strict object so
|
|
@@ -368,8 +693,11 @@ export const workflowDocumentAgentStepFields = [
|
|
|
368
693
|
// payload keys are present and emits one targeted issue per failure mode (a
|
|
369
694
|
// plain union would surface every branch's errors at once). The `agent`
|
|
370
695
|
// keyword is declared only so the reserved-keyword case produces a clear
|
|
371
|
-
// message instead of a generic "unrecognized key".
|
|
372
|
-
|
|
696
|
+
// message instead of a generic "unrecognized key". The `tool`, `connection`,
|
|
697
|
+
// `with`, and tool-step `outputs` mapping form are declared the same way: they
|
|
698
|
+
// parse so their shape is checked, then any step carrying a reserved tool field
|
|
699
|
+
// is rejected until the tool step kind exists.
|
|
700
|
+
const workflowDocumentStepBaseSchema = z.strictObject({
|
|
373
701
|
key: z.string().min(1).optional().meta({
|
|
374
702
|
description: 'Stable step key for dependencies and outputs.'
|
|
375
703
|
}),
|
|
@@ -398,6 +726,7 @@ export const workflowDocumentStepSchema = z.strictObject({
|
|
|
398
726
|
description: 'Agent harness. When omitted, Shipfox uses the workspace default harness, or `pi` when none is configured.'
|
|
399
727
|
}),
|
|
400
728
|
thinking: agentThinkingFieldSchema.optional(),
|
|
729
|
+
session: workflowDocumentSessionSchema.optional(),
|
|
401
730
|
provider: z.string().min(1).optional().meta({
|
|
402
731
|
description: 'Model provider ID for an agent step. It requires `prompt` and is not valid on a run step.'
|
|
403
732
|
}),
|
|
@@ -410,16 +739,24 @@ export const workflowDocumentStepSchema = z.strictObject({
|
|
|
410
739
|
agent: z.unknown().optional().meta({
|
|
411
740
|
description: 'Reserved keyword. It is rejected; use `prompt` to define an agent step.'
|
|
412
741
|
}),
|
|
742
|
+
tool: z.string().min(1).optional().meta({
|
|
743
|
+
description: 'Literal integration tool id for a tool step. It is rejected; tool steps are not available yet.'
|
|
744
|
+
}),
|
|
745
|
+
connection: z.string().min(1).optional().meta({
|
|
746
|
+
description: 'Literal integration connection slug for a tool step. It is rejected; tool steps are not available yet.'
|
|
747
|
+
}),
|
|
748
|
+
with: workflowDocumentToolStepWithSchema.optional(),
|
|
413
749
|
gate: workflowDocumentStepGateSchema.optional().meta({
|
|
414
750
|
description: 'Success gate and optional restart behavior after the step runs.'
|
|
415
751
|
}),
|
|
416
752
|
env: workflowDocumentEnvSchema.optional().meta({
|
|
417
753
|
description: 'Environment variables for a run step. They are not valid on an agent step.'
|
|
418
754
|
}),
|
|
419
|
-
outputs:
|
|
420
|
-
description: 'Named output declarations produced by this step.'
|
|
755
|
+
outputs: workflowDocumentStepOutputsFieldSchema.optional().meta({
|
|
756
|
+
description: 'Named output declarations produced by this step, or on a tool step a mapping of output keys to exactly one $' + '{{ }} expression over `result`. Tool steps are not available yet.'
|
|
421
757
|
})
|
|
422
|
-
})
|
|
758
|
+
});
|
|
759
|
+
export const workflowDocumentStepSchema = workflowDocumentStepBaseSchema.superRefine((step, ctx)=>{
|
|
423
760
|
if (step.agent !== undefined) {
|
|
424
761
|
ctx.addIssue({
|
|
425
762
|
code: 'custom',
|
|
@@ -430,6 +767,26 @@ export const workflowDocumentStepSchema = z.strictObject({
|
|
|
430
767
|
});
|
|
431
768
|
return;
|
|
432
769
|
}
|
|
770
|
+
const reservedToolField = step.tool !== undefined ? 'tool' : step.connection !== undefined ? 'connection' : undefined;
|
|
771
|
+
if (reservedToolField !== undefined || step.with !== undefined) {
|
|
772
|
+
ctx.addIssue({
|
|
773
|
+
code: 'custom',
|
|
774
|
+
path: [
|
|
775
|
+
reservedToolField ?? 'with'
|
|
776
|
+
],
|
|
777
|
+
message: 'Tool steps are not available yet.'
|
|
778
|
+
});
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
if (step.outputs !== undefined && stepOutputsAreMappingForm(step.outputs)) {
|
|
782
|
+
ctx.addIssue({
|
|
783
|
+
code: 'custom',
|
|
784
|
+
path: [
|
|
785
|
+
'outputs'
|
|
786
|
+
],
|
|
787
|
+
message: 'The `outputs` mapping form is reserved for tool steps.'
|
|
788
|
+
});
|
|
789
|
+
}
|
|
433
790
|
if (step.checkout !== undefined) {
|
|
434
791
|
if (step.run !== undefined) {
|
|
435
792
|
ctx.addIssue({
|
|
@@ -502,7 +859,10 @@ export const workflowDocumentStepSchema = z.strictObject({
|
|
|
502
859
|
message: 'An agent step requires "prompt".'
|
|
503
860
|
});
|
|
504
861
|
}
|
|
505
|
-
})
|
|
862
|
+
}).transform(({ outputs, ...step })=>outputs === undefined ? step : {
|
|
863
|
+
...step,
|
|
864
|
+
outputs: outputs
|
|
865
|
+
});
|
|
506
866
|
const workflowDocumentJobOutputsSchema = nonEmptyRecordSchema(z.string().min(1)).superRefine((outputs, ctx)=>{
|
|
507
867
|
const entries = Object.keys(outputs).length;
|
|
508
868
|
if (entries > WORKFLOW_DOCUMENT_JOB_OUTPUTS_MAX_ENTRIES) {
|
|
@@ -565,14 +925,41 @@ export const workflowDocumentSchema = z.strictObject({
|
|
|
565
925
|
})
|
|
566
926
|
});
|
|
567
927
|
function maxJsonDepth(value) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
928
|
+
let maximumDepth = 0;
|
|
929
|
+
const activeObjects = new Set();
|
|
930
|
+
const pending = [
|
|
931
|
+
{
|
|
932
|
+
kind: 'enter',
|
|
933
|
+
value,
|
|
934
|
+
depth: 0
|
|
935
|
+
}
|
|
936
|
+
];
|
|
937
|
+
while(pending.length > 0){
|
|
938
|
+
const current = pending.pop();
|
|
939
|
+
if (current === undefined) continue;
|
|
940
|
+
if (current.kind === 'leave') {
|
|
941
|
+
activeObjects.delete(current.value);
|
|
942
|
+
continue;
|
|
943
|
+
}
|
|
944
|
+
if (current.value === null || typeof current.value !== 'object') continue;
|
|
945
|
+
if (activeObjects.has(current.value)) continue;
|
|
946
|
+
activeObjects.add(current.value);
|
|
947
|
+
const depth = current.depth + 1;
|
|
948
|
+
maximumDepth = Math.max(maximumDepth, depth);
|
|
949
|
+
pending.push({
|
|
950
|
+
kind: 'leave',
|
|
951
|
+
value: current.value
|
|
952
|
+
});
|
|
953
|
+
const children = Object.values(current.value);
|
|
954
|
+
for(let index = children.length - 1; index >= 0; index -= 1){
|
|
955
|
+
pending.push({
|
|
956
|
+
kind: 'enter',
|
|
957
|
+
value: children[index],
|
|
958
|
+
depth
|
|
959
|
+
});
|
|
960
|
+
}
|
|
572
961
|
}
|
|
573
|
-
|
|
574
|
-
if (entries.length === 0) return 1;
|
|
575
|
-
return 1 + Math.max(...entries.map(maxJsonDepth));
|
|
962
|
+
return maximumDepth;
|
|
576
963
|
}
|
|
577
964
|
function isJsonSchemaDocument(value) {
|
|
578
965
|
return typeof value === 'boolean' || typeof value === 'object' && value !== null && !Array.isArray(value);
|