@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.js CHANGED
@@ -76,16 +76,21 @@ var LunnoaApiError = class extends Error {
76
76
  method;
77
77
  /** Path of the failed request (without the base URL). */
78
78
  path;
79
+ /** Present on definition-sync 409s when the server sends the current revision. */
80
+ currentRevision;
79
81
  constructor(args) {
80
82
  const detail = extractMessage(args.body);
83
+ const currentRevision = extractCurrentRevision(args.body);
84
+ const shown = [detail, currentRevision ? `currentRevision: ${currentRevision}` : void 0].filter(Boolean).join(" ");
81
85
  super(
82
- `Lunnoa API request failed: ${args.method} ${args.path} \u2192 ${args.status}${detail ? ` (${detail})` : ""}`
86
+ `Lunnoa API request failed: ${args.method} ${args.path} \u2192 ${args.status}${shown ? ` (${shown})` : ""}`
83
87
  );
84
88
  this.name = "LunnoaApiError";
85
89
  this.status = args.status;
86
90
  this.body = args.body;
87
91
  this.method = args.method;
88
92
  this.path = args.path;
93
+ this.currentRevision = currentRevision;
89
94
  }
90
95
  /** True for 401 responses (missing, revoked, or expired credential). */
91
96
  get isUnauthorized() {
@@ -104,6 +109,11 @@ var LunnoaApiError = class extends Error {
104
109
  return this.status === 429;
105
110
  }
106
111
  };
112
+ function extractCurrentRevision(body) {
113
+ if (!body || typeof body !== "object") return void 0;
114
+ const revision = body.currentRevision;
115
+ return typeof revision === "string" ? revision : void 0;
116
+ }
107
117
  function extractMessage(body) {
108
118
  if (body && typeof body === "object") {
109
119
  const record = body;
@@ -580,9 +590,27 @@ var ActionsResource = class {
580
590
  }
581
591
  };
582
592
 
593
+ // src/define/files.ts
594
+ function hasFileAgentSignals(record) {
595
+ if (record.instructions && typeof record.instructions === "object") return true;
596
+ if (record.settings && typeof record.settings === "object") return true;
597
+ 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) {
598
+ return true;
599
+ }
600
+ return Array.isArray(record.tools) && record.tools.some(
601
+ (tool) => tool && typeof tool === "object" && !("kind" in tool) && !("use" in tool)
602
+ );
603
+ }
604
+ function isAgentFileShape(config) {
605
+ if (!config || typeof config !== "object") return false;
606
+ return hasFileAgentSignals(config);
607
+ }
608
+
583
609
  // src/define/index.ts
584
610
  var DEFINE_KINDS = {
611
+ connection: "connection",
585
612
  action: "action",
613
+ trigger: "trigger",
586
614
  workflow: "workflow"};
587
615
  var PATH_CONDITIONS = [
588
616
  "contains",
@@ -711,10 +739,24 @@ function unwrapTool(tool) {
711
739
  }
712
740
  return tool;
713
741
  }
742
+ function syncFields(options) {
743
+ return {
744
+ ...options?.baseRevision ? { baseRevision: options.baseRevision } : {},
745
+ ...options?.force ? { force: true } : {}
746
+ };
747
+ }
748
+ function idsOf(links) {
749
+ if (!links) return void 0;
750
+ return links.map((link) => link.id);
751
+ }
714
752
  function agentDefinitionToUpsert(def, options) {
753
+ if (isAgentFileShape(def)) {
754
+ return fileAgentToUpsert(def, options);
755
+ }
715
756
  const tools = [];
716
757
  const workflowIds = [...options?.workflowIds ?? []];
717
- for (const tool of def.tools ?? []) {
758
+ const authored = def;
759
+ for (const tool of authored.tools ?? []) {
718
760
  const unwrapped = unwrapTool(tool);
719
761
  if (unwrapped.kind === DEFINE_KINDS.workflow) {
720
762
  continue;
@@ -730,17 +772,64 @@ function agentDefinitionToUpsert(def, options) {
730
772
  });
731
773
  }
732
774
  }
775
+ return {
776
+ slug: authored.slug,
777
+ name: authored.name,
778
+ description: authored.description,
779
+ instructions: authored.instructions,
780
+ model: authored.model,
781
+ aiConnectionId: authored.aiConnection?.id,
782
+ connectionIds: authored.connections?.map((c) => c.id).filter((id) => Boolean(id)),
783
+ workflowIds: workflowIds.length ? workflowIds : void 0,
784
+ tools,
785
+ codeSource: options?.codeSource ?? "generated",
786
+ ...syncFields(options)
787
+ };
788
+ }
789
+ function fileAgentToUpsert(def, options) {
790
+ if (typeof def.instructions !== "string") {
791
+ throw new Error(
792
+ "Agent instructions are still a file reference. The CLI resolves these before push."
793
+ );
794
+ }
795
+ const tools = [];
796
+ for (const tool of def.tools ?? []) {
797
+ const action = {
798
+ id: tool.id,
799
+ appId: tool.appId,
800
+ connection: tool.connection?.id ? { id: tool.connection.id } : void 0
801
+ };
802
+ tools.push({
803
+ key: tool.key,
804
+ appId: resolveActionAppId(action),
805
+ actionId: resolveActionId(action),
806
+ connectionId: tool.connection?.id,
807
+ name: tool.name,
808
+ description: tool.description,
809
+ input: tool.input
810
+ });
811
+ }
733
812
  return {
734
813
  slug: def.slug,
735
814
  name: def.name,
736
- description: def.description,
815
+ // A file is the whole definition. Omitted description and model are empty.
816
+ description: def.description ?? null,
737
817
  instructions: def.instructions,
738
- model: def.model,
818
+ model: def.model ?? null,
739
819
  aiConnectionId: def.aiConnection?.id,
740
- connectionIds: def.connections?.map((c) => c.id).filter((id) => Boolean(id)),
741
- workflowIds: workflowIds.length ? workflowIds : void 0,
742
- tools,
743
- managedByCode: true
820
+ ...def.settings ? { settings: def.settings } : {},
821
+ ...def.tools ? { tools } : {},
822
+ ...def.workflows ? { workflows: def.workflows } : {},
823
+ ...def.agents ? { agents: def.agents } : {},
824
+ ...def.connections ? { connectionIds: idsOf(def.connections) } : {},
825
+ ...def.knowledge ? { knowledgeIds: idsOf(def.knowledge) } : {},
826
+ ...def.variables ? { variableIds: idsOf(def.variables) } : {},
827
+ ...def.objects ? { objectIds: idsOf(def.objects) } : {},
828
+ ...def.objectTypes ? { objectTypeIds: idsOf(def.objectTypes) } : {},
829
+ ...def.mcpServers ? { mcpServers: def.mcpServers } : {},
830
+ ...def.a2aAgents ? { a2aAgentIds: idsOf(def.a2aAgents) } : {},
831
+ codeSource: "synced",
832
+ ...syncFields(options)
744
833
  };
745
834
  }
746
835
  var AgentsResource = class {
@@ -760,15 +849,22 @@ var AgentsResource = class {
760
849
  query: serializeListQuery(options)
761
850
  });
762
851
  }
852
+ /** Exported definition for `lunnoa/agents/<slug>.json`. */
853
+ getDefinition(slug) {
854
+ return this.http.get(
855
+ `/api/agents/slug/${encodeURIComponent(slug)}/definition`
856
+ );
857
+ }
763
858
  /**
764
859
  * Upsert an agent by slug inside the workspace on the API key.
765
860
  * Prefer the CLI (`npx @lunnoa/client agents deploy`) for happy-path deploy.
766
861
  */
767
862
  upsertBySlug(body, options) {
768
863
  const payload = "kind" in body && body.kind === "agent" ? agentDefinitionToUpsert(body, options) : body;
769
- return this.http.post("/api/agents/upsert", {
770
- body: payload
771
- });
864
+ return this.http.post(
865
+ "/api/agents/upsert",
866
+ { body: payload }
867
+ );
772
868
  }
773
869
  };
774
870
 
@@ -1892,6 +1988,11 @@ function stepFromDefinition(step) {
1892
1988
  }
1893
1989
  function normalizeStartRunInput(input) {
1894
1990
  if (isWorkflowDefinition(input)) {
1991
+ if ("format" in input && input.format === "graph") {
1992
+ throw new Error(
1993
+ "runs.start runs straight lines only. Push the graph workflow, then execute it."
1994
+ );
1995
+ }
1895
1996
  return {
1896
1997
  steps: input.steps.map(stepFromDefinition),
1897
1998
  name: input.name ?? input.slug
@@ -2083,6 +2184,18 @@ var WorkflowAppsResource = class {
2083
2184
  }
2084
2185
  };
2085
2186
 
2187
+ // src/resources/code-sync.ts
2188
+ var CodeSyncResource = class {
2189
+ constructor(http) {
2190
+ this.http = http;
2191
+ }
2192
+ http;
2193
+ /** Workspace id and whether synced resources are locked in the UI. */
2194
+ context() {
2195
+ return this.http.get("/api/code-sync/context");
2196
+ }
2197
+ };
2198
+
2086
2199
  // src/resources/workflows.ts
2087
2200
  function triggerToUpsertInput(trigger) {
2088
2201
  return {
@@ -2095,8 +2208,38 @@ function triggerToUpsertInput(trigger) {
2095
2208
  ...trigger.description ? { description: trigger.description } : {}
2096
2209
  };
2097
2210
  }
2211
+ function asAction(use) {
2212
+ if (use && typeof use === "object" && "kind" in use && use.kind === DEFINE_KINDS.action) {
2213
+ return use;
2214
+ }
2215
+ const fileUse = use;
2216
+ return {
2217
+ kind: DEFINE_KINDS.action,
2218
+ id: fileUse.id,
2219
+ appId: fileUse.appId,
2220
+ connection: fileUse.connection?.id ? { kind: DEFINE_KINDS.connection, id: fileUse.connection.id } : void 0
2221
+ };
2222
+ }
2223
+ function asTrigger(trigger) {
2224
+ if ("kind" in trigger && trigger.kind === DEFINE_KINDS.trigger) {
2225
+ return trigger;
2226
+ }
2227
+ const fileTrigger = trigger;
2228
+ return {
2229
+ kind: DEFINE_KINDS.trigger,
2230
+ id: fileTrigger.id,
2231
+ appId: fileTrigger.appId,
2232
+ connection: fileTrigger.connection?.id ? { kind: DEFINE_KINDS.connection, id: fileTrigger.connection.id } : void 0,
2233
+ input: fileTrigger.input,
2234
+ inputs: fileTrigger.inputs,
2235
+ name: fileTrigger.name,
2236
+ description: fileTrigger.description
2237
+ };
2238
+ }
2098
2239
  function stepToUpsertInput(step) {
2099
- const use = step.use;
2240
+ const use = asAction(step.use);
2241
+ const retry = "retrySettings" in step ? step.retrySettings : void 0;
2242
+ const continueOnFailure = "continueOnFailure" in step ? step.continueOnFailure : void 0;
2100
2243
  return {
2101
2244
  ...step.key ? { key: step.key } : {},
2102
2245
  appId: resolveActionAppId(use),
@@ -2107,25 +2250,60 @@ function stepToUpsertInput(step) {
2107
2250
  ...step.input ?? {}
2108
2251
  },
2109
2252
  name: step.name ?? use.name,
2110
- description: use.description,
2253
+ description: step.description ?? use.description,
2254
+ ...retry ? { retrySettings: retry } : {},
2255
+ ...continueOnFailure ? { continueOnFailure } : {},
2111
2256
  ...step.paths?.length ? {
2112
2257
  paths: step.paths.map((path) => ({
2113
2258
  label: path.label,
2114
2259
  ...path.when ? { when: path.when } : {},
2115
- steps: path.steps.map(stepToUpsertInput)
2260
+ steps: path.steps.map(
2261
+ (nested) => stepToUpsertInput(nested)
2262
+ )
2116
2263
  }))
2117
2264
  } : {}
2118
2265
  };
2119
2266
  }
2120
- function workflowDefinitionToUpsert(def) {
2267
+ function syncFields2(options) {
2121
2268
  return {
2122
- slug: def.slug,
2123
- name: def.name,
2124
- description: def.description,
2125
- isActive: def.isActive,
2126
- ...def.trigger ? { trigger: triggerToUpsertInput(def.trigger) } : {},
2127
- managedByCode: true,
2128
- steps: def.steps.map(stepToUpsertInput)
2269
+ ...options?.baseRevision ? { baseRevision: options.baseRevision } : {},
2270
+ ...options?.force ? { force: true } : {}
2271
+ };
2272
+ }
2273
+ function workflowDefinitionToUpsert(def, options) {
2274
+ if ("format" in def && def.format === "graph") {
2275
+ return {
2276
+ slug: def.slug,
2277
+ name: def.name,
2278
+ // A file is the whole definition: an omitted description is empty, not "leave it".
2279
+ description: def.description ?? null,
2280
+ codeSource: "synced",
2281
+ graph: { nodes: def.nodes, edges: def.edges ?? [] },
2282
+ ...syncFields2(options)
2283
+ };
2284
+ }
2285
+ if ("format" in def && def.format === "steps") {
2286
+ const file = def;
2287
+ return {
2288
+ slug: file.slug,
2289
+ name: file.name,
2290
+ description: file.description ?? null,
2291
+ ...file.trigger ? { trigger: triggerToUpsertInput(asTrigger(file.trigger)) } : {},
2292
+ codeSource: "synced",
2293
+ steps: file.steps.map((step) => stepToUpsertInput(step)),
2294
+ ...syncFields2(options)
2295
+ };
2296
+ }
2297
+ const authored = def;
2298
+ return {
2299
+ slug: authored.slug,
2300
+ name: authored.name,
2301
+ description: authored.description,
2302
+ isActive: authored.isActive,
2303
+ ...authored.trigger ? { trigger: triggerToUpsertInput(authored.trigger) } : {},
2304
+ codeSource: options?.codeSource ?? "generated",
2305
+ ...syncFields2(options),
2306
+ steps: authored.steps.map((step) => stepToUpsertInput(step))
2129
2307
  };
2130
2308
  }
2131
2309
  var WorkflowsResource = class {
@@ -2150,15 +2328,22 @@ var WorkflowsResource = class {
2150
2328
  query: serializeListQuery(options)
2151
2329
  });
2152
2330
  }
2331
+ /** Exported definition for `lunnoa/workflows/<slug>.json`. */
2332
+ getDefinition(slug) {
2333
+ return this.http.get(
2334
+ `/api/workflows/slug/${encodeURIComponent(slug)}/definition`
2335
+ );
2336
+ }
2153
2337
  /**
2154
2338
  * Upsert a workflow by slug inside the workspace on the API key.
2155
2339
  * Prefer the CLI (`npx @lunnoa/client workflows deploy`) for happy-path deploy.
2156
2340
  */
2157
- upsertBySlug(body) {
2158
- const payload = "kind" in body && body.kind === "workflow" ? workflowDefinitionToUpsert(body) : body;
2159
- return this.http.post("/api/workflows/upsert", {
2160
- body: payload
2161
- });
2341
+ upsertBySlug(body, options) {
2342
+ const payload = "kind" in body && body.kind === "workflow" ? workflowDefinitionToUpsert(body, options) : body;
2343
+ return this.http.post(
2344
+ "/api/workflows/upsert",
2345
+ { body: payload }
2346
+ );
2162
2347
  }
2163
2348
  /**
2164
2349
  * Starts a new execution of the workflow with the given trigger inputs.
@@ -2224,6 +2409,8 @@ var LunnoaClient = class {
2224
2409
  aiConnections;
2225
2410
  workflowApps;
2226
2411
  discovery;
2412
+ /** Workspace id for the definitions lock, and the code-sync lock flag. */
2413
+ codeSync;
2227
2414
  constructor(options) {
2228
2415
  this.http = new HttpClient(options);
2229
2416
  this.auth = new AuthResource(this.http, this.http.tokenStore);
@@ -2247,6 +2434,7 @@ var LunnoaClient = class {
2247
2434
  this.connections = new ConnectionsResource(this.http);
2248
2435
  this.aiConnections = new AiConnectionsResource(this.http);
2249
2436
  this.discovery = new DiscoveryResource(this.http);
2437
+ this.codeSync = new CodeSyncResource(this.http);
2250
2438
  }
2251
2439
  };
2252
2440