@anna-ai/cli 0.1.28 → 0.1.29
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/dist/cli.js
CHANGED
|
@@ -592,8 +592,8 @@ executa.command("install").description("Install a local-dev shim for an Executa
|
|
|
592
592
|
});
|
|
593
593
|
process.exit(code);
|
|
594
594
|
});
|
|
595
|
-
executa.command("dev").description("Run one Executa plugin in isolation (REPL or one-shot describe/invoke)").option("--dir <path>", "Executa project dir (default: CWD)").option("--spec <spec>", "Override discovery: comma-separated key=value (tool_id=...,type=...,command=\"...\")").option("--describe", "Print MANIFEST and exit", false).option("--health", "Print health and exit", false).option("--invoke <tool>", "Invoke one tool and exit").option("--args <json>", "JSON object passed as tool arguments", "{}").option("--json", "One-shot: emit compact JSON (no banners)", false).option("--no-sampling", "Hard-disable sampling reverse RPC (returns sampling_disabled)").option("--mock-sampling <fixture>", "Serve canned sampling responses from a JSONL fixture (offline)").option("--app-slug <slug>", "Forward sampling to nexus on behalf of this dev AnnaApp slug").option("--sampling-account <host>", "Saved account host for nexus sampling (default: current)").option("--no-agent", "Hard-disable agent reverse RPC (returns agent_not_granted)").option("--mock-agent <fixture>", "Serve canned agent/* responses from a JSONL fixture (offline)").option("--agent-account <host>", "Saved account host for nexus agent (default: --sampling-account or current)").option("--storage <mode>", "Storage backend: off | memory | mock | real (default: memory)").option("--mock-storage <fixture>", "Serve canned storage/* + files/* responses from a JSONL fixture").option("--storage-account <host>", "Saved account host for nexus storage (default: --sampling-account or current)").option("--storage-scopes <list>", "Comma-separated scopes for real storage tokens (default: user,app,tool)").option("--no-image", "Hard-disable image reverse RPC (returns image_not_granted)").option("--mock-image <fixture>", "Serve canned image/generate + image/edit responses from a JSONL fixture").option("--image-account <host>", "Saved account host for nexus image (default: --sampling-account or current)").option("--no-upload", "Hard-disable host/uploadFile reverse RPC (returns upload_not_granted)").option("--mock-upload <fixture>", "Serve canned host/uploadFile responses from a JSONL fixture").option("--upload-account <host>", "Saved account host for nexus uploads (default: --sampling-account or current)").action(async (opts) => {
|
|
596
|
-
const { runExecutaDev } = await import("./executa-dev-
|
|
595
|
+
executa.command("dev").description("Run one Executa plugin in isolation (REPL or one-shot describe/invoke)").option("--dir <path>", "Executa project dir (default: CWD)").option("--spec <spec>", "Override discovery: comma-separated key=value (tool_id=...,type=...,command=\"...\")").option("--describe", "Print MANIFEST and exit", false).option("--health", "Print health and exit", false).option("--invoke <tool>", "Invoke one tool and exit").option("--args <json>", "JSON object passed as tool arguments", "{}").option("--json", "One-shot: emit compact JSON (no banners)", false).option("--no-sampling", "Hard-disable sampling reverse RPC (returns sampling_disabled)").option("--mock-sampling <fixture>", "Serve canned sampling responses from a JSONL fixture (offline)").option("--sampling-unsupported-format", "Simulate a model without json_schema support — exercises responseFormat onUnsupported branches (-32010 / downgrade)").option("--app-slug <slug>", "Forward sampling to nexus on behalf of this dev AnnaApp slug").option("--sampling-account <host>", "Saved account host for nexus sampling (default: current)").option("--no-agent", "Hard-disable agent reverse RPC (returns agent_not_granted)").option("--mock-agent <fixture>", "Serve canned agent/* responses from a JSONL fixture (offline)").option("--agent-account <host>", "Saved account host for nexus agent (default: --sampling-account or current)").option("--storage <mode>", "Storage backend: off | memory | mock | real (default: memory)").option("--mock-storage <fixture>", "Serve canned storage/* + files/* responses from a JSONL fixture").option("--storage-account <host>", "Saved account host for nexus storage (default: --sampling-account or current)").option("--storage-scopes <list>", "Comma-separated scopes for real storage tokens (default: user,app,tool)").option("--no-image", "Hard-disable image reverse RPC (returns image_not_granted)").option("--mock-image <fixture>", "Serve canned image/generate + image/edit responses from a JSONL fixture").option("--image-account <host>", "Saved account host for nexus image (default: --sampling-account or current)").option("--no-upload", "Hard-disable host/uploadFile reverse RPC (returns upload_not_granted)").option("--mock-upload <fixture>", "Serve canned host/uploadFile responses from a JSONL fixture").option("--upload-account <host>", "Saved account host for nexus uploads (default: --sampling-account or current)").action(async (opts) => {
|
|
596
|
+
const { runExecutaDev } = await import("./executa-dev-DEpBrEIH.js");
|
|
597
597
|
const storageMode = opts.storage === void 0 ? void 0 : (() => {
|
|
598
598
|
const m = opts.storage;
|
|
599
599
|
if (m === "off" || m === "memory" || m === "mock" || m === "real") return m;
|
|
@@ -610,6 +610,7 @@ executa.command("dev").description("Run one Executa plugin in isolation (REPL or
|
|
|
610
610
|
json: opts.json,
|
|
611
611
|
noSampling: opts.sampling === false,
|
|
612
612
|
mockSampling: opts.mockSampling,
|
|
613
|
+
samplingUnsupportedFormat: opts.samplingUnsupportedFormat,
|
|
613
614
|
appSlug: opts.appSlug,
|
|
614
615
|
samplingAccount: opts.samplingAccount,
|
|
615
616
|
noAgent: opts.agent === false,
|
|
@@ -57,14 +57,17 @@ async function runExecutaDev(opts) {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
const { SamplingBridge } = await import("./sampling-
|
|
60
|
+
const { SamplingBridge } = await import("./sampling-DwV7VPfT.js");
|
|
61
|
+
const simulateUnsupportedResponseFormat = opts.samplingUnsupportedFormat;
|
|
61
62
|
const sampling = opts.noSampling ? new SamplingBridge({ mode: "off" }) : opts.mockSampling ? new SamplingBridge({
|
|
62
63
|
mode: "mock",
|
|
63
|
-
mockFile: opts.mockSampling
|
|
64
|
+
mockFile: opts.mockSampling,
|
|
65
|
+
simulateUnsupportedResponseFormat
|
|
64
66
|
}) : effectiveAppSlug ? new SamplingBridge({
|
|
65
67
|
mode: "real",
|
|
66
68
|
account: opts.samplingAccount,
|
|
67
|
-
appSlug: effectiveAppSlug
|
|
69
|
+
appSlug: effectiveAppSlug,
|
|
70
|
+
simulateUnsupportedResponseFormat
|
|
68
71
|
}) : new SamplingBridge({ mode: "off" });
|
|
69
72
|
const { AgentBridge } = await import("./agent-CaZVCPs6.js");
|
|
70
73
|
const agent = opts.noAgent ? new AgentBridge({ mode: "off" }) : opts.mockAgent ? new AgentBridge({
|
|
@@ -20,8 +20,43 @@ var SamplingBridge = class {
|
|
|
20
20
|
}
|
|
21
21
|
async createMessage(req) {
|
|
22
22
|
if (this.opts.mode === "off") throw withCode(new Error("sampling disabled — rerun without `--no-sampling`"), -32008);
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
validateResponseFormat(req);
|
|
24
|
+
const { effective, downgraded } = this.negotiateResponseFormat(req);
|
|
25
|
+
const result = this.opts.mode === "mock" ? this.mockMessage(req) : await this.realMessage(req, effective);
|
|
26
|
+
if (req.responseFormat) result._meta = {
|
|
27
|
+
...result._meta ?? {},
|
|
28
|
+
responseFormat: {
|
|
29
|
+
requested: req.responseFormat.type,
|
|
30
|
+
applied: effective?.type ?? null,
|
|
31
|
+
structuredValid: isParseableJson(result.content?.text),
|
|
32
|
+
downgraded
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Local stand-in for the nexus gate's capability negotiation. With
|
|
39
|
+
* `simulateUnsupportedResponseFormat` the bridge behaves like a model
|
|
40
|
+
* without `supports_structured_output`; otherwise everything is
|
|
41
|
+
* "supported" (mock fixtures decide what comes back anyway).
|
|
42
|
+
*/
|
|
43
|
+
negotiateResponseFormat(req) {
|
|
44
|
+
let effective = req.responseFormat;
|
|
45
|
+
let downgraded = false;
|
|
46
|
+
if (effective?.type === "json_schema" && this.opts.simulateUnsupportedResponseFormat) {
|
|
47
|
+
const on = req.onUnsupported ?? "error";
|
|
48
|
+
if (on === "error") {
|
|
49
|
+
const err = withCode(new Error("model 'mock-model' does not support json_schema response format (simulated)"), -32010);
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
if (on === "json_object") effective = { type: "json_object" };
|
|
53
|
+
else effective = void 0;
|
|
54
|
+
downgraded = true;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
effective,
|
|
58
|
+
downgraded
|
|
59
|
+
};
|
|
25
60
|
}
|
|
26
61
|
mockMessage(req) {
|
|
27
62
|
const content = collectContent(req);
|
|
@@ -69,10 +104,11 @@ var SamplingBridge = class {
|
|
|
69
104
|
};
|
|
70
105
|
return this.cachedSession;
|
|
71
106
|
}
|
|
72
|
-
async realMessage(req) {
|
|
107
|
+
async realMessage(req, effectiveResponseFormat) {
|
|
73
108
|
const acc = this.account();
|
|
74
109
|
const session = await this.mint();
|
|
75
110
|
const body = mcpToCompleteBody(req);
|
|
111
|
+
if (effectiveResponseFormat) body.response_format = effectiveResponseFormat;
|
|
76
112
|
const res = await fetch(`${canonicalHost(acc.host)}/api/v1/copilot/app/complete`, {
|
|
77
113
|
method: "POST",
|
|
78
114
|
headers: {
|
|
@@ -93,6 +129,59 @@ function withCode(err, code) {
|
|
|
93
129
|
err.rpcCode = code;
|
|
94
130
|
return err;
|
|
95
131
|
}
|
|
132
|
+
const RF_TYPES = new Set(["json_object", "json_schema"]);
|
|
133
|
+
const RF_ON_UNSUPPORTED = new Set([
|
|
134
|
+
"error",
|
|
135
|
+
"json_object",
|
|
136
|
+
"text"
|
|
137
|
+
]);
|
|
138
|
+
const RF_MAX_SCHEMA_BYTES = 32 * 1024;
|
|
139
|
+
const RF_MAX_SCHEMA_DEPTH = 8;
|
|
140
|
+
const RF_MAX_SCHEMA_NODES = 512;
|
|
141
|
+
const RF_NAME_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
142
|
+
function invalid(message) {
|
|
143
|
+
return withCode(new Error(message), -32004);
|
|
144
|
+
}
|
|
145
|
+
/** Same hard limits the platform enforces — local dev fails identically. */
|
|
146
|
+
function validateResponseFormat(req) {
|
|
147
|
+
const on = req.onUnsupported;
|
|
148
|
+
if (on != null && !RF_ON_UNSUPPORTED.has(on)) throw invalid(`'onUnsupported' must be one of ${[...RF_ON_UNSUPPORTED].sort().join(", ")}`);
|
|
149
|
+
const rf = req.responseFormat;
|
|
150
|
+
if (rf == null) return;
|
|
151
|
+
if (typeof rf !== "object" || Array.isArray(rf)) throw invalid("'responseFormat' must be an object");
|
|
152
|
+
if (!RF_TYPES.has(rf.type)) throw invalid(`responseFormat.type must be one of ${[...RF_TYPES].sort().join(", ")}`);
|
|
153
|
+
if (rf.type === "json_schema") {
|
|
154
|
+
const js = rf.json_schema;
|
|
155
|
+
if (js == null || typeof js !== "object" || Array.isArray(js)) throw invalid("responseFormat.json_schema must be an object");
|
|
156
|
+
if (typeof js.name !== "string" || !RF_NAME_RE.test(js.name)) throw invalid("json_schema.name must match ^[a-zA-Z0-9_-]{1,64}$");
|
|
157
|
+
if (js.strict !== void 0 && typeof js.strict !== "boolean") throw invalid("json_schema.strict must be a bool");
|
|
158
|
+
const schema = js.schema;
|
|
159
|
+
if (schema == null || typeof schema !== "object" || Array.isArray(schema)) throw invalid("json_schema.schema must be an object");
|
|
160
|
+
enforceSchemaLimits(schema);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function enforceSchemaLimits(schema) {
|
|
164
|
+
const raw = JSON.stringify(schema);
|
|
165
|
+
if (Buffer.byteLength(raw, "utf8") > RF_MAX_SCHEMA_BYTES) throw invalid(`json_schema.schema too large (> ${RF_MAX_SCHEMA_BYTES} bytes)`);
|
|
166
|
+
let nodes = 0;
|
|
167
|
+
const walk = (node, depth) => {
|
|
168
|
+
if (depth > RF_MAX_SCHEMA_DEPTH) throw invalid(`json_schema nesting too deep (> ${RF_MAX_SCHEMA_DEPTH})`);
|
|
169
|
+
nodes += 1;
|
|
170
|
+
if (nodes > RF_MAX_SCHEMA_NODES) throw invalid(`json_schema too many nodes (> ${RF_MAX_SCHEMA_NODES})`);
|
|
171
|
+
if (Array.isArray(node)) for (const v of node) walk(v, depth + 1);
|
|
172
|
+
else if (node !== null && typeof node === "object") for (const v of Object.values(node)) walk(v, depth + 1);
|
|
173
|
+
};
|
|
174
|
+
walk(schema, 0);
|
|
175
|
+
}
|
|
176
|
+
function isParseableJson(text) {
|
|
177
|
+
if (typeof text !== "string") return false;
|
|
178
|
+
try {
|
|
179
|
+
JSON.parse(text);
|
|
180
|
+
return true;
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
96
185
|
/** Collect a single string view of a sampling request — used for mock matching. */
|
|
97
186
|
function collectContent(req) {
|
|
98
187
|
const parts = [];
|