@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-Deo8ag5o.js");
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-BcML4teS.js");
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
- if (this.opts.mode === "mock") return this.mockMessage(req);
24
- return this.realMessage(req);
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 = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anna-ai/cli",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "Anna App developer CLI: scaffold, validate, harness (Phase 2 MVP: init + validate).",
5
5
  "license": "MIT",
6
6
  "type": "module",