@lunnoa/client 2.4.0 → 3.0.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/dist/react.cjs CHANGED
@@ -78,16 +78,21 @@ var LunnoaApiError = class extends Error {
78
78
  method;
79
79
  /** Path of the failed request (without the base URL). */
80
80
  path;
81
+ /** Present on definition-sync 409s when the server sends the current revision. */
82
+ currentRevision;
81
83
  constructor(args) {
82
84
  const detail = extractMessage(args.body);
85
+ const currentRevision = extractCurrentRevision(args.body);
86
+ const shown = [detail, currentRevision ? `currentRevision: ${currentRevision}` : void 0].filter(Boolean).join(" ");
83
87
  super(
84
- `Lunnoa API request failed: ${args.method} ${args.path} \u2192 ${args.status}${detail ? ` (${detail})` : ""}`
88
+ `Lunnoa API request failed: ${args.method} ${args.path} \u2192 ${args.status}${shown ? ` (${shown})` : ""}`
85
89
  );
86
90
  this.name = "LunnoaApiError";
87
91
  this.status = args.status;
88
92
  this.body = args.body;
89
93
  this.method = args.method;
90
94
  this.path = args.path;
95
+ this.currentRevision = currentRevision;
91
96
  }
92
97
  /** True for 401 responses (missing, revoked, or expired credential). */
93
98
  get isUnauthorized() {
@@ -106,6 +111,11 @@ var LunnoaApiError = class extends Error {
106
111
  return this.status === 429;
107
112
  }
108
113
  };
114
+ function extractCurrentRevision(body) {
115
+ if (!body || typeof body !== "object") return void 0;
116
+ const revision = body.currentRevision;
117
+ return typeof revision === "string" ? revision : void 0;
118
+ }
109
119
  function extractMessage(body) {
110
120
  if (body && typeof body === "object") {
111
121
  const record = body;
@@ -582,10 +592,55 @@ var ActionsResource = class {
582
592
  }
583
593
  };
584
594
 
595
+ // src/define/files.ts
596
+ function hasFileAgentSignals(record) {
597
+ if (record.instructions && typeof record.instructions === "object") return true;
598
+ if (record.settings && typeof record.settings === "object") return true;
599
+ if ("workflows" in record || "agents" in record || "knowledge" in record || "variables" in record || "objects" in record || "objectTypes" in record || "mcpServers" in record || "a2aAgents" in record) {
600
+ return true;
601
+ }
602
+ return Array.isArray(record.tools) && record.tools.some(
603
+ (tool) => tool && typeof tool === "object" && !("kind" in tool) && !("use" in tool)
604
+ );
605
+ }
606
+ function isAgentFileShape(config) {
607
+ if (!config || typeof config !== "object") return false;
608
+ return hasFileAgentSignals(config);
609
+ }
610
+
585
611
  // src/define/index.ts
586
612
  var DEFINE_KINDS = {
613
+ connection: "connection",
587
614
  action: "action",
615
+ trigger: "trigger",
588
616
  workflow: "workflow"};
617
+ var PATH_CONDITIONS = [
618
+ "contains",
619
+ "does_not_contain",
620
+ "equals",
621
+ "does_not_equal",
622
+ "exists",
623
+ "does_not_exist",
624
+ "starts_with",
625
+ "ends_with",
626
+ "is_empty",
627
+ "is_not_empty",
628
+ "comes_before",
629
+ "comes_after",
630
+ "is_in_the_past",
631
+ "is_in_the_future",
632
+ "is_true",
633
+ "is_false",
634
+ "is_truthy",
635
+ "is_falsey",
636
+ "is_greater_than",
637
+ "is_less_than",
638
+ "is_greater_than_or_equal_to",
639
+ "is_less_than_or_equal_to",
640
+ "matches_regex",
641
+ "does_not_match_regex"
642
+ ];
643
+ new Set(PATH_CONDITIONS);
589
644
  function resolveActionAppId(action) {
590
645
  if (action.appId?.trim()) {
591
646
  return action.appId.trim();
@@ -686,10 +741,24 @@ function unwrapTool(tool) {
686
741
  }
687
742
  return tool;
688
743
  }
744
+ function syncFields(options) {
745
+ return {
746
+ ...options?.baseRevision ? { baseRevision: options.baseRevision } : {},
747
+ ...options?.force ? { force: true } : {}
748
+ };
749
+ }
750
+ function idsOf(links) {
751
+ if (!links) return void 0;
752
+ return links.map((link) => link.id);
753
+ }
689
754
  function agentDefinitionToUpsert(def, options) {
755
+ if (isAgentFileShape(def)) {
756
+ return fileAgentToUpsert(def, options);
757
+ }
690
758
  const tools = [];
691
759
  const workflowIds = [...options?.workflowIds ?? []];
692
- for (const tool of def.tools ?? []) {
760
+ const authored = def;
761
+ for (const tool of authored.tools ?? []) {
693
762
  const unwrapped = unwrapTool(tool);
694
763
  if (unwrapped.kind === DEFINE_KINDS.workflow) {
695
764
  continue;
@@ -705,17 +774,64 @@ function agentDefinitionToUpsert(def, options) {
705
774
  });
706
775
  }
707
776
  }
777
+ return {
778
+ slug: authored.slug,
779
+ name: authored.name,
780
+ description: authored.description,
781
+ instructions: authored.instructions,
782
+ model: authored.model,
783
+ aiConnectionId: authored.aiConnection?.id,
784
+ connectionIds: authored.connections?.map((c) => c.id).filter((id) => Boolean(id)),
785
+ workflowIds: workflowIds.length ? workflowIds : void 0,
786
+ tools,
787
+ codeSource: options?.codeSource ?? "generated",
788
+ ...syncFields(options)
789
+ };
790
+ }
791
+ function fileAgentToUpsert(def, options) {
792
+ if (typeof def.instructions !== "string") {
793
+ throw new Error(
794
+ "Agent instructions are still a file reference. The CLI resolves these before push."
795
+ );
796
+ }
797
+ const tools = [];
798
+ for (const tool of def.tools ?? []) {
799
+ const action = {
800
+ id: tool.id,
801
+ appId: tool.appId,
802
+ connection: tool.connection?.id ? { id: tool.connection.id } : void 0
803
+ };
804
+ tools.push({
805
+ key: tool.key,
806
+ appId: resolveActionAppId(action),
807
+ actionId: resolveActionId(action),
808
+ connectionId: tool.connection?.id,
809
+ name: tool.name,
810
+ description: tool.description,
811
+ input: tool.input
812
+ });
813
+ }
708
814
  return {
709
815
  slug: def.slug,
710
816
  name: def.name,
711
- description: def.description,
817
+ // A file is the whole definition. Omitted description and model are empty.
818
+ description: def.description ?? null,
712
819
  instructions: def.instructions,
713
- model: def.model,
820
+ model: def.model ?? null,
714
821
  aiConnectionId: def.aiConnection?.id,
715
- connectionIds: def.connections?.map((c) => c.id).filter((id) => Boolean(id)),
716
- workflowIds: workflowIds.length ? workflowIds : void 0,
717
- tools,
718
- managedByCode: true
822
+ ...def.settings ? { settings: def.settings } : {},
823
+ ...def.tools ? { tools } : {},
824
+ ...def.workflows ? { workflows: def.workflows } : {},
825
+ ...def.agents ? { agents: def.agents } : {},
826
+ ...def.connections ? { connectionIds: idsOf(def.connections) } : {},
827
+ ...def.knowledge ? { knowledgeIds: idsOf(def.knowledge) } : {},
828
+ ...def.variables ? { variableIds: idsOf(def.variables) } : {},
829
+ ...def.objects ? { objectIds: idsOf(def.objects) } : {},
830
+ ...def.objectTypes ? { objectTypeIds: idsOf(def.objectTypes) } : {},
831
+ ...def.mcpServers ? { mcpServers: def.mcpServers } : {},
832
+ ...def.a2aAgents ? { a2aAgentIds: idsOf(def.a2aAgents) } : {},
833
+ codeSource: "synced",
834
+ ...syncFields(options)
719
835
  };
720
836
  }
721
837
  var AgentsResource = class {
@@ -735,15 +851,22 @@ var AgentsResource = class {
735
851
  query: serializeListQuery(options)
736
852
  });
737
853
  }
854
+ /** Exported definition for `lunnoa/agents/<slug>.json`. */
855
+ getDefinition(slug) {
856
+ return this.http.get(
857
+ `/api/agents/slug/${encodeURIComponent(slug)}/definition`
858
+ );
859
+ }
738
860
  /**
739
861
  * Upsert an agent by slug inside the workspace on the API key.
740
862
  * Prefer the CLI (`npx @lunnoa/client agents deploy`) for happy-path deploy.
741
863
  */
742
864
  upsertBySlug(body, options) {
743
865
  const payload = "kind" in body && body.kind === "agent" ? agentDefinitionToUpsert(body, options) : body;
744
- return this.http.post("/api/agents/upsert", {
745
- body: payload
746
- });
866
+ return this.http.post(
867
+ "/api/agents/upsert",
868
+ { body: payload }
869
+ );
747
870
  }
748
871
  };
749
872
 
@@ -1845,6 +1968,11 @@ function stepFromDefinition(step) {
1845
1968
  ...step.name ? { name: step.name } : {}
1846
1969
  };
1847
1970
  }
1971
+ if (step.paths?.length) {
1972
+ throw new Error(
1973
+ "runs.start runs straight lines only. Deploy workflows with paths, then execute them."
1974
+ );
1975
+ }
1848
1976
  const use = step.use;
1849
1977
  const input = {
1850
1978
  ...use.input ?? {},
@@ -1862,6 +1990,11 @@ function stepFromDefinition(step) {
1862
1990
  }
1863
1991
  function normalizeStartRunInput(input) {
1864
1992
  if (isWorkflowDefinition(input)) {
1993
+ if ("format" in input && input.format === "graph") {
1994
+ throw new Error(
1995
+ "runs.start runs straight lines only. Push the graph workflow, then execute it."
1996
+ );
1997
+ }
1865
1998
  return {
1866
1999
  steps: input.steps.map(stepFromDefinition),
1867
2000
  name: input.name ?? input.slug
@@ -2053,6 +2186,18 @@ var WorkflowAppsResource = class {
2053
2186
  }
2054
2187
  };
2055
2188
 
2189
+ // src/resources/code-sync.ts
2190
+ var CodeSyncResource = class {
2191
+ constructor(http) {
2192
+ this.http = http;
2193
+ }
2194
+ http;
2195
+ /** Workspace id and whether synced resources are locked in the UI. */
2196
+ context() {
2197
+ return this.http.get("/api/code-sync/context");
2198
+ }
2199
+ };
2200
+
2056
2201
  // src/resources/workflows.ts
2057
2202
  function triggerToUpsertInput(trigger) {
2058
2203
  return {
@@ -2065,8 +2210,38 @@ function triggerToUpsertInput(trigger) {
2065
2210
  ...trigger.description ? { description: trigger.description } : {}
2066
2211
  };
2067
2212
  }
2213
+ function asAction(use) {
2214
+ if (use && typeof use === "object" && "kind" in use && use.kind === DEFINE_KINDS.action) {
2215
+ return use;
2216
+ }
2217
+ const fileUse = use;
2218
+ return {
2219
+ kind: DEFINE_KINDS.action,
2220
+ id: fileUse.id,
2221
+ appId: fileUse.appId,
2222
+ connection: fileUse.connection?.id ? { kind: DEFINE_KINDS.connection, id: fileUse.connection.id } : void 0
2223
+ };
2224
+ }
2225
+ function asTrigger(trigger) {
2226
+ if ("kind" in trigger && trigger.kind === DEFINE_KINDS.trigger) {
2227
+ return trigger;
2228
+ }
2229
+ const fileTrigger = trigger;
2230
+ return {
2231
+ kind: DEFINE_KINDS.trigger,
2232
+ id: fileTrigger.id,
2233
+ appId: fileTrigger.appId,
2234
+ connection: fileTrigger.connection?.id ? { kind: DEFINE_KINDS.connection, id: fileTrigger.connection.id } : void 0,
2235
+ input: fileTrigger.input,
2236
+ inputs: fileTrigger.inputs,
2237
+ name: fileTrigger.name,
2238
+ description: fileTrigger.description
2239
+ };
2240
+ }
2068
2241
  function stepToUpsertInput(step) {
2069
- const use = step.use;
2242
+ const use = asAction(step.use);
2243
+ const retry = "retrySettings" in step ? step.retrySettings : void 0;
2244
+ const continueOnFailure = "continueOnFailure" in step ? step.continueOnFailure : void 0;
2070
2245
  return {
2071
2246
  ...step.key ? { key: step.key } : {},
2072
2247
  appId: resolveActionAppId(use),
@@ -2077,18 +2252,60 @@ function stepToUpsertInput(step) {
2077
2252
  ...step.input ?? {}
2078
2253
  },
2079
2254
  name: step.name ?? use.name,
2080
- description: use.description
2255
+ description: step.description ?? use.description,
2256
+ ...retry ? { retrySettings: retry } : {},
2257
+ ...continueOnFailure ? { continueOnFailure } : {},
2258
+ ...step.paths?.length ? {
2259
+ paths: step.paths.map((path) => ({
2260
+ label: path.label,
2261
+ ...path.when ? { when: path.when } : {},
2262
+ steps: path.steps.map(
2263
+ (nested) => stepToUpsertInput(nested)
2264
+ )
2265
+ }))
2266
+ } : {}
2081
2267
  };
2082
2268
  }
2083
- function workflowDefinitionToUpsert(def) {
2269
+ function syncFields2(options) {
2084
2270
  return {
2085
- slug: def.slug,
2086
- name: def.name,
2087
- description: def.description,
2088
- isActive: def.isActive,
2089
- ...def.trigger ? { trigger: triggerToUpsertInput(def.trigger) } : {},
2090
- managedByCode: true,
2091
- steps: def.steps.map(stepToUpsertInput)
2271
+ ...options?.baseRevision ? { baseRevision: options.baseRevision } : {},
2272
+ ...options?.force ? { force: true } : {}
2273
+ };
2274
+ }
2275
+ function workflowDefinitionToUpsert(def, options) {
2276
+ if ("format" in def && def.format === "graph") {
2277
+ return {
2278
+ slug: def.slug,
2279
+ name: def.name,
2280
+ // A file is the whole definition: an omitted description is empty, not "leave it".
2281
+ description: def.description ?? null,
2282
+ codeSource: "synced",
2283
+ graph: { nodes: def.nodes, edges: def.edges ?? [] },
2284
+ ...syncFields2(options)
2285
+ };
2286
+ }
2287
+ if ("format" in def && def.format === "steps") {
2288
+ const file = def;
2289
+ return {
2290
+ slug: file.slug,
2291
+ name: file.name,
2292
+ description: file.description ?? null,
2293
+ ...file.trigger ? { trigger: triggerToUpsertInput(asTrigger(file.trigger)) } : {},
2294
+ codeSource: "synced",
2295
+ steps: file.steps.map((step) => stepToUpsertInput(step)),
2296
+ ...syncFields2(options)
2297
+ };
2298
+ }
2299
+ const authored = def;
2300
+ return {
2301
+ slug: authored.slug,
2302
+ name: authored.name,
2303
+ description: authored.description,
2304
+ isActive: authored.isActive,
2305
+ ...authored.trigger ? { trigger: triggerToUpsertInput(authored.trigger) } : {},
2306
+ codeSource: options?.codeSource ?? "generated",
2307
+ ...syncFields2(options),
2308
+ steps: authored.steps.map((step) => stepToUpsertInput(step))
2092
2309
  };
2093
2310
  }
2094
2311
  var WorkflowsResource = class {
@@ -2113,15 +2330,22 @@ var WorkflowsResource = class {
2113
2330
  query: serializeListQuery(options)
2114
2331
  });
2115
2332
  }
2333
+ /** Exported definition for `lunnoa/workflows/<slug>.json`. */
2334
+ getDefinition(slug) {
2335
+ return this.http.get(
2336
+ `/api/workflows/slug/${encodeURIComponent(slug)}/definition`
2337
+ );
2338
+ }
2116
2339
  /**
2117
2340
  * Upsert a workflow by slug inside the workspace on the API key.
2118
2341
  * Prefer the CLI (`npx @lunnoa/client workflows deploy`) for happy-path deploy.
2119
2342
  */
2120
- upsertBySlug(body) {
2121
- const payload = "kind" in body && body.kind === "workflow" ? workflowDefinitionToUpsert(body) : body;
2122
- return this.http.post("/api/workflows/upsert", {
2123
- body: payload
2124
- });
2343
+ upsertBySlug(body, options) {
2344
+ const payload = "kind" in body && body.kind === "workflow" ? workflowDefinitionToUpsert(body, options) : body;
2345
+ return this.http.post(
2346
+ "/api/workflows/upsert",
2347
+ { body: payload }
2348
+ );
2125
2349
  }
2126
2350
  /**
2127
2351
  * Starts a new execution of the workflow with the given trigger inputs.
@@ -2187,6 +2411,8 @@ var LunnoaClient = class {
2187
2411
  aiConnections;
2188
2412
  workflowApps;
2189
2413
  discovery;
2414
+ /** Workspace id for the definitions lock, and the code-sync lock flag. */
2415
+ codeSync;
2190
2416
  constructor(options) {
2191
2417
  this.http = new HttpClient(options);
2192
2418
  this.auth = new AuthResource(this.http, this.http.tokenStore);
@@ -2210,6 +2436,7 @@ var LunnoaClient = class {
2210
2436
  this.connections = new ConnectionsResource(this.http);
2211
2437
  this.aiConnections = new AiConnectionsResource(this.http);
2212
2438
  this.discovery = new DiscoveryResource(this.http);
2439
+ this.codeSync = new CodeSyncResource(this.http);
2213
2440
  }
2214
2441
  };
2215
2442