@gigaai/newton 0.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 +142 -0
- package/dist/acp.js +257 -0
- package/dist/cli.js +220 -0
- package/dist/executor.js +287 -0
- package/dist/journal.js +72 -0
- package/dist/progress.js +61 -0
- package/dist/providers.js +255 -0
- package/dist/schema.js +184 -0
- package/dist/version.js +1 -0
- package/package.json +48 -0
package/dist/schema.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal JSON Schema validation — just the subset workflow authors actually
|
|
3
|
+
* use for structured output (type/properties/required/items/enum/anyOf).
|
|
4
|
+
* Deliberately not a full validator; swap for ajv if this ever falls short.
|
|
5
|
+
*/
|
|
6
|
+
export function validateAgainstSchema(value, schema, path = "$") {
|
|
7
|
+
const errors = [];
|
|
8
|
+
if (schema.const !== undefined && JSON.stringify(value) !== JSON.stringify(schema.const)) {
|
|
9
|
+
return [`${path}: expected const ${JSON.stringify(schema.const)}`];
|
|
10
|
+
}
|
|
11
|
+
if (Array.isArray(schema.enum) && !schema.enum.some((e) => JSON.stringify(e) === JSON.stringify(value))) {
|
|
12
|
+
return [`${path}: expected one of ${JSON.stringify(schema.enum)}`];
|
|
13
|
+
}
|
|
14
|
+
const variants = (schema.anyOf ?? schema.oneOf);
|
|
15
|
+
if (Array.isArray(variants)) {
|
|
16
|
+
const matches = variants.some((v) => validateAgainstSchema(value, v, path).length === 0);
|
|
17
|
+
if (!matches)
|
|
18
|
+
errors.push(`${path}: matched no variant of anyOf/oneOf`);
|
|
19
|
+
return errors;
|
|
20
|
+
}
|
|
21
|
+
const type = schema.type;
|
|
22
|
+
if (type) {
|
|
23
|
+
const types = Array.isArray(type) ? type : [type];
|
|
24
|
+
if (!types.some((t) => matchesType(value, t))) {
|
|
25
|
+
return [`${path}: expected ${types.join("|")}, got ${describe(value)}`];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (isObject(value)) {
|
|
29
|
+
const required = schema.required ?? [];
|
|
30
|
+
for (const key of required) {
|
|
31
|
+
if (!(key in value))
|
|
32
|
+
errors.push(`${path}: missing required property "${key}"`);
|
|
33
|
+
}
|
|
34
|
+
const properties = schema.properties ?? {};
|
|
35
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
36
|
+
if (key in value)
|
|
37
|
+
errors.push(...validateAgainstSchema(value[key], propSchema, `${path}.${key}`));
|
|
38
|
+
}
|
|
39
|
+
if (schema.additionalProperties === false) {
|
|
40
|
+
for (const key of Object.keys(value)) {
|
|
41
|
+
if (!(key in properties))
|
|
42
|
+
errors.push(`${path}: unexpected property "${key}"`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
if (typeof schema.minItems === "number" && value.length < schema.minItems) {
|
|
48
|
+
errors.push(`${path}: expected at least ${schema.minItems} items`);
|
|
49
|
+
}
|
|
50
|
+
if (typeof schema.maxItems === "number" && value.length > schema.maxItems) {
|
|
51
|
+
errors.push(`${path}: expected at most ${schema.maxItems} items`);
|
|
52
|
+
}
|
|
53
|
+
if (isObject(schema.items)) {
|
|
54
|
+
value.forEach((item, i) => {
|
|
55
|
+
errors.push(...validateAgainstSchema(item, schema.items, `${path}[${i}]`));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (typeof value === "number") {
|
|
60
|
+
if (typeof schema.minimum === "number" && value < schema.minimum) {
|
|
61
|
+
errors.push(`${path}: ${value} < minimum ${schema.minimum}`);
|
|
62
|
+
}
|
|
63
|
+
if (typeof schema.maximum === "number" && value > schema.maximum) {
|
|
64
|
+
errors.push(`${path}: ${value} > maximum ${schema.maximum}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (typeof value === "string" && typeof schema.pattern === "string") {
|
|
68
|
+
if (!new RegExp(schema.pattern).test(value)) {
|
|
69
|
+
errors.push(`${path}: does not match pattern ${schema.pattern}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return errors;
|
|
73
|
+
}
|
|
74
|
+
function matchesType(value, type) {
|
|
75
|
+
switch (type) {
|
|
76
|
+
case "string":
|
|
77
|
+
return typeof value === "string";
|
|
78
|
+
case "number":
|
|
79
|
+
return typeof value === "number";
|
|
80
|
+
case "integer":
|
|
81
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
82
|
+
case "boolean":
|
|
83
|
+
return typeof value === "boolean";
|
|
84
|
+
case "array":
|
|
85
|
+
return Array.isArray(value);
|
|
86
|
+
case "object":
|
|
87
|
+
return isObject(value);
|
|
88
|
+
case "null":
|
|
89
|
+
return value === null;
|
|
90
|
+
default:
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function isObject(value) {
|
|
95
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
96
|
+
}
|
|
97
|
+
function describe(value) {
|
|
98
|
+
if (value === null)
|
|
99
|
+
return "null";
|
|
100
|
+
if (Array.isArray(value))
|
|
101
|
+
return "array";
|
|
102
|
+
return typeof value;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Pull a JSON value out of agent prose. Tries the whole text, then fenced
|
|
106
|
+
* blocks, then the last balanced {...} or [...] span — agents often preface
|
|
107
|
+
* the payload with commentary despite instructions not to.
|
|
108
|
+
*/
|
|
109
|
+
export function extractJson(text) {
|
|
110
|
+
const trimmed = text.trim();
|
|
111
|
+
const direct = tryParse(trimmed);
|
|
112
|
+
if (direct !== INVALID)
|
|
113
|
+
return direct;
|
|
114
|
+
const fences = [...trimmed.matchAll(/```(?:json)?\s*\n?([\s\S]*?)```/g)];
|
|
115
|
+
for (const fence of fences.reverse()) {
|
|
116
|
+
const parsed = tryParse(fence[1].trim());
|
|
117
|
+
if (parsed !== INVALID)
|
|
118
|
+
return parsed;
|
|
119
|
+
}
|
|
120
|
+
for (const open of ["{", "["]) {
|
|
121
|
+
const start = trimmed.lastIndexOf(open);
|
|
122
|
+
if (start === -1)
|
|
123
|
+
continue;
|
|
124
|
+
const candidate = balancedSlice(trimmed, start);
|
|
125
|
+
if (candidate) {
|
|
126
|
+
const parsed = tryParse(candidate);
|
|
127
|
+
if (parsed !== INVALID)
|
|
128
|
+
return parsed;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return INVALID;
|
|
132
|
+
}
|
|
133
|
+
export const INVALID = Symbol("invalid-json");
|
|
134
|
+
function tryParse(text) {
|
|
135
|
+
try {
|
|
136
|
+
return JSON.parse(text);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return INVALID;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export function balancedSlice(text, start) {
|
|
143
|
+
const open = text[start];
|
|
144
|
+
const close = open === "{" ? "}" : "]";
|
|
145
|
+
let depth = 0;
|
|
146
|
+
let inString = false;
|
|
147
|
+
let escaped = false;
|
|
148
|
+
for (let i = start; i < text.length; i++) {
|
|
149
|
+
const ch = text[i];
|
|
150
|
+
if (escaped) {
|
|
151
|
+
escaped = false;
|
|
152
|
+
}
|
|
153
|
+
else if (ch === "\\") {
|
|
154
|
+
escaped = inString;
|
|
155
|
+
}
|
|
156
|
+
else if (ch === '"') {
|
|
157
|
+
inString = !inString;
|
|
158
|
+
}
|
|
159
|
+
else if (!inString) {
|
|
160
|
+
if (ch === open)
|
|
161
|
+
depth++;
|
|
162
|
+
else if (ch === close && --depth === 0)
|
|
163
|
+
return text.slice(start, i + 1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
export function structuredOutputInstruction(schema) {
|
|
169
|
+
return [
|
|
170
|
+
"",
|
|
171
|
+
"## Output format (mandatory)",
|
|
172
|
+
"Your final message must be ONLY a single JSON value matching this JSON Schema — no prose, no markdown fences:",
|
|
173
|
+
JSON.stringify(schema, null, 2),
|
|
174
|
+
].join("\n");
|
|
175
|
+
}
|
|
176
|
+
export function structuredOutputRetryPrompt(errors, schema) {
|
|
177
|
+
return [
|
|
178
|
+
"Your previous response was not valid against the required JSON Schema:",
|
|
179
|
+
...errors.slice(0, 10).map((e) => `- ${e}`),
|
|
180
|
+
"",
|
|
181
|
+
"Respond again with ONLY a single JSON value matching this schema — no prose, no fences:",
|
|
182
|
+
JSON.stringify(schema, null, 2),
|
|
183
|
+
].join("\n");
|
|
184
|
+
}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = "0.3.0";
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gigaai/newton",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Deterministic multi-agent workflow executor. Write workflows in plain JavaScript; Newton drives coding agents over the Agent Client Protocol.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/GigaML/newton.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/GigaML/newton#readme",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"acp",
|
|
13
|
+
"agent-client-protocol",
|
|
14
|
+
"agents",
|
|
15
|
+
"workflow",
|
|
16
|
+
"orchestration",
|
|
17
|
+
"codex",
|
|
18
|
+
"claude",
|
|
19
|
+
"cli"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"bin": {
|
|
23
|
+
"newton": "dist/cli.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc -p tsconfig.json && node -e \"require('fs').chmodSync('dist/cli.js', 0o755)\"",
|
|
36
|
+
"check": "npm run build && node dist/cli.js validate examples/hello.js && node dist/cli.js doctor --json",
|
|
37
|
+
"prepare": "npm run build",
|
|
38
|
+
"prepublishOnly": "npm run build"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@agentclientprotocol/codex-acp": "^1.1.0",
|
|
42
|
+
"@agentclientprotocol/sdk": "^1.2.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^24.0.0",
|
|
46
|
+
"typescript": "^5.8.3"
|
|
47
|
+
}
|
|
48
|
+
}
|