@lunnoa/client 2.5.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,9 +592,27 @@ 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"};
589
617
  var PATH_CONDITIONS = [
590
618
  "contains",
@@ -713,10 +741,24 @@ function unwrapTool(tool) {
713
741
  }
714
742
  return tool;
715
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
+ }
716
754
  function agentDefinitionToUpsert(def, options) {
755
+ if (isAgentFileShape(def)) {
756
+ return fileAgentToUpsert(def, options);
757
+ }
717
758
  const tools = [];
718
759
  const workflowIds = [...options?.workflowIds ?? []];
719
- for (const tool of def.tools ?? []) {
760
+ const authored = def;
761
+ for (const tool of authored.tools ?? []) {
720
762
  const unwrapped = unwrapTool(tool);
721
763
  if (unwrapped.kind === DEFINE_KINDS.workflow) {
722
764
  continue;
@@ -732,17 +774,64 @@ function agentDefinitionToUpsert(def, options) {
732
774
  });
733
775
  }
734
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
+ }
735
814
  return {
736
815
  slug: def.slug,
737
816
  name: def.name,
738
- description: def.description,
817
+ // A file is the whole definition. Omitted description and model are empty.
818
+ description: def.description ?? null,
739
819
  instructions: def.instructions,
740
- model: def.model,
820
+ model: def.model ?? null,
741
821
  aiConnectionId: def.aiConnection?.id,
742
- connectionIds: def.connections?.map((c) => c.id).filter((id) => Boolean(id)),
743
- workflowIds: workflowIds.length ? workflowIds : void 0,
744
- tools,
745
- 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)
746
835
  };
747
836
  }
748
837
  var AgentsResource = class {
@@ -762,15 +851,22 @@ var AgentsResource = class {
762
851
  query: serializeListQuery(options)
763
852
  });
764
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
+ }
765
860
  /**
766
861
  * Upsert an agent by slug inside the workspace on the API key.
767
862
  * Prefer the CLI (`npx @lunnoa/client agents deploy`) for happy-path deploy.
768
863
  */
769
864
  upsertBySlug(body, options) {
770
865
  const payload = "kind" in body && body.kind === "agent" ? agentDefinitionToUpsert(body, options) : body;
771
- return this.http.post("/api/agents/upsert", {
772
- body: payload
773
- });
866
+ return this.http.post(
867
+ "/api/agents/upsert",
868
+ { body: payload }
869
+ );
774
870
  }
775
871
  };
776
872
 
@@ -1894,6 +1990,11 @@ function stepFromDefinition(step) {
1894
1990
  }
1895
1991
  function normalizeStartRunInput(input) {
1896
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
+ }
1897
1998
  return {
1898
1999
  steps: input.steps.map(stepFromDefinition),
1899
2000
  name: input.name ?? input.slug
@@ -2085,6 +2186,18 @@ var WorkflowAppsResource = class {
2085
2186
  }
2086
2187
  };
2087
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
+
2088
2201
  // src/resources/workflows.ts
2089
2202
  function triggerToUpsertInput(trigger) {
2090
2203
  return {
@@ -2097,8 +2210,38 @@ function triggerToUpsertInput(trigger) {
2097
2210
  ...trigger.description ? { description: trigger.description } : {}
2098
2211
  };
2099
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
+ }
2100
2241
  function stepToUpsertInput(step) {
2101
- 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;
2102
2245
  return {
2103
2246
  ...step.key ? { key: step.key } : {},
2104
2247
  appId: resolveActionAppId(use),
@@ -2109,25 +2252,60 @@ function stepToUpsertInput(step) {
2109
2252
  ...step.input ?? {}
2110
2253
  },
2111
2254
  name: step.name ?? use.name,
2112
- description: use.description,
2255
+ description: step.description ?? use.description,
2256
+ ...retry ? { retrySettings: retry } : {},
2257
+ ...continueOnFailure ? { continueOnFailure } : {},
2113
2258
  ...step.paths?.length ? {
2114
2259
  paths: step.paths.map((path) => ({
2115
2260
  label: path.label,
2116
2261
  ...path.when ? { when: path.when } : {},
2117
- steps: path.steps.map(stepToUpsertInput)
2262
+ steps: path.steps.map(
2263
+ (nested) => stepToUpsertInput(nested)
2264
+ )
2118
2265
  }))
2119
2266
  } : {}
2120
2267
  };
2121
2268
  }
2122
- function workflowDefinitionToUpsert(def) {
2269
+ function syncFields2(options) {
2123
2270
  return {
2124
- slug: def.slug,
2125
- name: def.name,
2126
- description: def.description,
2127
- isActive: def.isActive,
2128
- ...def.trigger ? { trigger: triggerToUpsertInput(def.trigger) } : {},
2129
- managedByCode: true,
2130
- 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))
2131
2309
  };
2132
2310
  }
2133
2311
  var WorkflowsResource = class {
@@ -2152,15 +2330,22 @@ var WorkflowsResource = class {
2152
2330
  query: serializeListQuery(options)
2153
2331
  });
2154
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
+ }
2155
2339
  /**
2156
2340
  * Upsert a workflow by slug inside the workspace on the API key.
2157
2341
  * Prefer the CLI (`npx @lunnoa/client workflows deploy`) for happy-path deploy.
2158
2342
  */
2159
- upsertBySlug(body) {
2160
- const payload = "kind" in body && body.kind === "workflow" ? workflowDefinitionToUpsert(body) : body;
2161
- return this.http.post("/api/workflows/upsert", {
2162
- body: payload
2163
- });
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
+ );
2164
2349
  }
2165
2350
  /**
2166
2351
  * Starts a new execution of the workflow with the given trigger inputs.
@@ -2226,6 +2411,8 @@ var LunnoaClient = class {
2226
2411
  aiConnections;
2227
2412
  workflowApps;
2228
2413
  discovery;
2414
+ /** Workspace id for the definitions lock, and the code-sync lock flag. */
2415
+ codeSync;
2229
2416
  constructor(options) {
2230
2417
  this.http = new HttpClient(options);
2231
2418
  this.auth = new AuthResource(this.http, this.http.tokenStore);
@@ -2249,6 +2436,7 @@ var LunnoaClient = class {
2249
2436
  this.connections = new ConnectionsResource(this.http);
2250
2437
  this.aiConnections = new AiConnectionsResource(this.http);
2251
2438
  this.discovery = new DiscoveryResource(this.http);
2439
+ this.codeSync = new CodeSyncResource(this.http);
2252
2440
  }
2253
2441
  };
2254
2442