@modelrelay/sdk 4.0.0 → 5.3.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/node.cjs CHANGED
@@ -52,7 +52,7 @@ var import_child_process = require("child_process");
52
52
  // package.json
53
53
  var package_default = {
54
54
  name: "@modelrelay/sdk",
55
- version: "4.0.0",
55
+ version: "5.3.0",
56
56
  description: "TypeScript SDK for the ModelRelay API",
57
57
  type: "module",
58
58
  main: "dist/index.cjs",
@@ -293,7 +293,8 @@ var ToolRegistry = class {
293
293
  var ToolNames = {
294
294
  FS_READ_FILE: "fs.read_file",
295
295
  FS_LIST_FILES: "fs.list_files",
296
- FS_SEARCH: "fs.search"
296
+ FS_SEARCH: "fs.search",
297
+ FS_EDIT: "fs.edit"
297
298
  };
298
299
  var FSDefaults = {
299
300
  MAX_READ_BYTES: 64e3,
@@ -411,6 +412,36 @@ var LocalFSToolPack = class {
411
412
  required: ["query"]
412
413
  }
413
414
  }
415
+ },
416
+ {
417
+ type: "function",
418
+ function: {
419
+ name: ToolNames.FS_EDIT,
420
+ description: "Replace exact string matches in a file. Returns a confirmation with affected line numbers.",
421
+ parameters: {
422
+ type: "object",
423
+ properties: {
424
+ path: {
425
+ type: "string",
426
+ description: "Workspace-relative path to the file (e.g., 'src/index.ts')"
427
+ },
428
+ old_string: {
429
+ type: "string",
430
+ description: "Exact string to find and replace"
431
+ },
432
+ new_string: {
433
+ type: "string",
434
+ description: "Replacement string (may be empty)"
435
+ },
436
+ replace_all: {
437
+ type: "boolean",
438
+ default: false,
439
+ description: "Replace all occurrences (default: replace first only)"
440
+ }
441
+ },
442
+ required: ["path", "old_string", "new_string"]
443
+ }
444
+ }
414
445
  }
415
446
  ];
416
447
  }
@@ -423,6 +454,7 @@ var LocalFSToolPack = class {
423
454
  registry.register(ToolNames.FS_READ_FILE, this.readFile.bind(this));
424
455
  registry.register(ToolNames.FS_LIST_FILES, this.listFiles.bind(this));
425
456
  registry.register(ToolNames.FS_SEARCH, this.search.bind(this));
457
+ registry.register(ToolNames.FS_EDIT, this.editFile.bind(this));
426
458
  return registry;
427
459
  }
428
460
  /**
@@ -542,6 +574,41 @@ var LocalFSToolPack = class {
542
574
  }
543
575
  return this.searchWithJS(query, absPath, maxMatches, call);
544
576
  }
577
+ async editFile(_args, call) {
578
+ const args = this.parseArgs(call, [
579
+ "path",
580
+ "old_string"
581
+ ]);
582
+ const func = call.function;
583
+ const relPath = this.requireString(args, "path", call);
584
+ const oldString = this.requireString(args, "old_string", call);
585
+ const newString = this.requireStringAllowEmpty(args, "new_string", call);
586
+ const replaceAll = this.optionalBoolean(args, "replace_all", call) ?? false;
587
+ const absPath = await this.resolveAndValidatePath(relPath, call);
588
+ const stat = await import_fs.promises.stat(absPath);
589
+ if (stat.isDirectory()) {
590
+ throw new Error(`fs.edit: path is a directory: ${relPath}`);
591
+ }
592
+ const data = await import_fs.promises.readFile(absPath);
593
+ if (!this.isValidUtf8(data)) {
594
+ throw new Error(`fs.edit: file is not valid UTF-8: ${relPath}`);
595
+ }
596
+ const contents = data.toString("utf-8");
597
+ const matches = this.findOccurrences(contents, oldString);
598
+ if (matches.length === 0) {
599
+ throw new Error("fs.edit: old_string not found");
600
+ }
601
+ if (!replaceAll && matches.length > 1) {
602
+ throw new Error("fs.edit: old_string appears multiple times");
603
+ }
604
+ const appliedMatches = replaceAll ? matches : matches.slice(0, 1);
605
+ const updated = replaceAll ? contents.split(oldString).join(newString) : contents.replace(oldString, newString);
606
+ await import_fs.promises.writeFile(absPath, updated);
607
+ const lineSpec = this.formatLineRanges(
608
+ this.buildLineRanges(contents, appliedMatches, oldString.length)
609
+ );
610
+ return `Edited ${relPath} (replacements=${appliedMatches.length}, lines ${lineSpec})`;
611
+ }
545
612
  // ========================================================================
546
613
  // Path Safety
547
614
  // ========================================================================
@@ -821,6 +888,16 @@ var LocalFSToolPack = class {
821
888
  }
822
889
  return value;
823
890
  }
891
+ requireStringAllowEmpty(args, key, call) {
892
+ const value = args[key];
893
+ if (value === void 0 || value === null) {
894
+ this.toolArgumentError(call, `${key} is required`);
895
+ }
896
+ if (typeof value !== "string") {
897
+ this.toolArgumentError(call, `${key} must be a string`);
898
+ }
899
+ return value;
900
+ }
824
901
  optionalString(args, key, call) {
825
902
  const value = args[key];
826
903
  if (value === void 0 || value === null) {
@@ -848,6 +925,82 @@ var LocalFSToolPack = class {
848
925
  }
849
926
  return value;
850
927
  }
928
+ optionalBoolean(args, key, call) {
929
+ const value = args[key];
930
+ if (value === void 0 || value === null) {
931
+ return void 0;
932
+ }
933
+ if (typeof value !== "boolean") {
934
+ this.toolArgumentError(call, `${key} must be a boolean`);
935
+ }
936
+ return value;
937
+ }
938
+ findOccurrences(haystack, needle) {
939
+ if (needle.length === 0) {
940
+ return [];
941
+ }
942
+ const out = [];
943
+ let idx = 0;
944
+ for (; ; ) {
945
+ const found = haystack.indexOf(needle, idx);
946
+ if (found === -1) {
947
+ break;
948
+ }
949
+ out.push(found);
950
+ idx = found + needle.length;
951
+ }
952
+ return out;
953
+ }
954
+ buildLineRanges(source, offsets, needleLength) {
955
+ if (offsets.length === 0) {
956
+ return [];
957
+ }
958
+ const newlineOffsets = [];
959
+ for (let i = 0; i < source.length; i++) {
960
+ if (source.charCodeAt(i) === 10) {
961
+ newlineOffsets.push(i);
962
+ }
963
+ }
964
+ return offsets.map((startIdx) => {
965
+ const endIdx = startIdx + Math.max(needleLength, 1) - 1;
966
+ return {
967
+ start: this.lineNumberAt(newlineOffsets, startIdx),
968
+ end: this.lineNumberAt(newlineOffsets, endIdx)
969
+ };
970
+ });
971
+ }
972
+ lineNumberAt(newlineOffsets, index) {
973
+ let lo = 0;
974
+ let hi = newlineOffsets.length;
975
+ while (lo < hi) {
976
+ const mid = lo + hi >> 1;
977
+ if (newlineOffsets[mid] < index) {
978
+ lo = mid + 1;
979
+ } else {
980
+ hi = mid;
981
+ }
982
+ }
983
+ return lo + 1;
984
+ }
985
+ formatLineRanges(ranges) {
986
+ if (ranges.length === 0) {
987
+ return "";
988
+ }
989
+ const merged = [];
990
+ for (const range of ranges) {
991
+ const last = merged[merged.length - 1];
992
+ if (!last || range.start > last.end + 1) {
993
+ merged.push({ ...range });
994
+ continue;
995
+ }
996
+ if (range.end > last.end) {
997
+ last.end = range.end;
998
+ }
999
+ }
1000
+ return merged.map(
1001
+ (range) => range.start === range.end ? `${range.start}` : `${range.start}-${range.end}`
1002
+ ).join(",");
1003
+ }
851
1004
  isValidUtf8(buffer) {
852
1005
  try {
853
1006
  const text = buffer.toString("utf-8");
package/dist/node.d.cts CHANGED
@@ -7,6 +7,7 @@ import { e as Tool, k as ToolRegistry } from './tools-bCt1QwNk.cjs';
7
7
  * - fs.read_file - Read workspace-relative files
8
8
  * - fs.list_files - List files recursively
9
9
  * - fs.search - Regex search (ripgrep or JS fallback)
10
+ * - fs.edit - Replace exact string matches in a file
10
11
  *
11
12
  * Safety features:
12
13
  * - Root sandbox with symlink resolution
@@ -23,6 +24,7 @@ declare const ToolNames: {
23
24
  readonly FS_READ_FILE: "fs.read_file";
24
25
  readonly FS_LIST_FILES: "fs.list_files";
25
26
  readonly FS_SEARCH: "fs.search";
27
+ readonly FS_EDIT: "fs.edit";
26
28
  };
27
29
  /** Default size limits and caps from wire contract. */
28
30
  declare const FSDefaults: {
@@ -103,6 +105,7 @@ declare class LocalFSToolPack {
103
105
  private readFile;
104
106
  private listFiles;
105
107
  private search;
108
+ private editFile;
106
109
  /**
107
110
  * Resolves a workspace-relative path and validates it stays within the sandbox.
108
111
  * @throws {ToolArgumentError} if path is invalid
@@ -116,8 +119,14 @@ declare class LocalFSToolPack {
116
119
  private parseArgs;
117
120
  private toolArgumentError;
118
121
  private requireString;
122
+ private requireStringAllowEmpty;
119
123
  private optionalString;
120
124
  private optionalPositiveInt;
125
+ private optionalBoolean;
126
+ private findOccurrences;
127
+ private buildLineRanges;
128
+ private lineNumberAt;
129
+ private formatLineRanges;
121
130
  private isValidUtf8;
122
131
  /**
123
132
  * Recursively walks a directory, calling visitor for each entry.
package/dist/node.d.ts CHANGED
@@ -7,6 +7,7 @@ import { e as Tool, k as ToolRegistry } from './tools-bCt1QwNk.js';
7
7
  * - fs.read_file - Read workspace-relative files
8
8
  * - fs.list_files - List files recursively
9
9
  * - fs.search - Regex search (ripgrep or JS fallback)
10
+ * - fs.edit - Replace exact string matches in a file
10
11
  *
11
12
  * Safety features:
12
13
  * - Root sandbox with symlink resolution
@@ -23,6 +24,7 @@ declare const ToolNames: {
23
24
  readonly FS_READ_FILE: "fs.read_file";
24
25
  readonly FS_LIST_FILES: "fs.list_files";
25
26
  readonly FS_SEARCH: "fs.search";
27
+ readonly FS_EDIT: "fs.edit";
26
28
  };
27
29
  /** Default size limits and caps from wire contract. */
28
30
  declare const FSDefaults: {
@@ -103,6 +105,7 @@ declare class LocalFSToolPack {
103
105
  private readFile;
104
106
  private listFiles;
105
107
  private search;
108
+ private editFile;
106
109
  /**
107
110
  * Resolves a workspace-relative path and validates it stays within the sandbox.
108
111
  * @throws {ToolArgumentError} if path is invalid
@@ -116,8 +119,14 @@ declare class LocalFSToolPack {
116
119
  private parseArgs;
117
120
  private toolArgumentError;
118
121
  private requireString;
122
+ private requireStringAllowEmpty;
119
123
  private optionalString;
120
124
  private optionalPositiveInt;
125
+ private optionalBoolean;
126
+ private findOccurrences;
127
+ private buildLineRanges;
128
+ private lineNumberAt;
129
+ private formatLineRanges;
121
130
  private isValidUtf8;
122
131
  /**
123
132
  * Recursively walks a directory, calling visitor for each entry.
package/dist/node.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  ToolArgumentError,
4
4
  ToolRegistry,
5
5
  ToolTypes
6
- } from "./chunk-5O4NJXLJ.js";
6
+ } from "./chunk-LZDGY24E.js";
7
7
  import "./chunk-MLKGABMK.js";
8
8
 
9
9
  // src/tools_local_fs.ts
@@ -13,7 +13,8 @@ import { spawn } from "child_process";
13
13
  var ToolNames = {
14
14
  FS_READ_FILE: "fs.read_file",
15
15
  FS_LIST_FILES: "fs.list_files",
16
- FS_SEARCH: "fs.search"
16
+ FS_SEARCH: "fs.search",
17
+ FS_EDIT: "fs.edit"
17
18
  };
18
19
  var FSDefaults = {
19
20
  MAX_READ_BYTES: 64e3,
@@ -131,6 +132,36 @@ var LocalFSToolPack = class {
131
132
  required: ["query"]
132
133
  }
133
134
  }
135
+ },
136
+ {
137
+ type: "function",
138
+ function: {
139
+ name: ToolNames.FS_EDIT,
140
+ description: "Replace exact string matches in a file. Returns a confirmation with affected line numbers.",
141
+ parameters: {
142
+ type: "object",
143
+ properties: {
144
+ path: {
145
+ type: "string",
146
+ description: "Workspace-relative path to the file (e.g., 'src/index.ts')"
147
+ },
148
+ old_string: {
149
+ type: "string",
150
+ description: "Exact string to find and replace"
151
+ },
152
+ new_string: {
153
+ type: "string",
154
+ description: "Replacement string (may be empty)"
155
+ },
156
+ replace_all: {
157
+ type: "boolean",
158
+ default: false,
159
+ description: "Replace all occurrences (default: replace first only)"
160
+ }
161
+ },
162
+ required: ["path", "old_string", "new_string"]
163
+ }
164
+ }
134
165
  }
135
166
  ];
136
167
  }
@@ -143,6 +174,7 @@ var LocalFSToolPack = class {
143
174
  registry.register(ToolNames.FS_READ_FILE, this.readFile.bind(this));
144
175
  registry.register(ToolNames.FS_LIST_FILES, this.listFiles.bind(this));
145
176
  registry.register(ToolNames.FS_SEARCH, this.search.bind(this));
177
+ registry.register(ToolNames.FS_EDIT, this.editFile.bind(this));
146
178
  return registry;
147
179
  }
148
180
  /**
@@ -262,6 +294,41 @@ var LocalFSToolPack = class {
262
294
  }
263
295
  return this.searchWithJS(query, absPath, maxMatches, call);
264
296
  }
297
+ async editFile(_args, call) {
298
+ const args = this.parseArgs(call, [
299
+ "path",
300
+ "old_string"
301
+ ]);
302
+ const func = call.function;
303
+ const relPath = this.requireString(args, "path", call);
304
+ const oldString = this.requireString(args, "old_string", call);
305
+ const newString = this.requireStringAllowEmpty(args, "new_string", call);
306
+ const replaceAll = this.optionalBoolean(args, "replace_all", call) ?? false;
307
+ const absPath = await this.resolveAndValidatePath(relPath, call);
308
+ const stat = await fs.stat(absPath);
309
+ if (stat.isDirectory()) {
310
+ throw new Error(`fs.edit: path is a directory: ${relPath}`);
311
+ }
312
+ const data = await fs.readFile(absPath);
313
+ if (!this.isValidUtf8(data)) {
314
+ throw new Error(`fs.edit: file is not valid UTF-8: ${relPath}`);
315
+ }
316
+ const contents = data.toString("utf-8");
317
+ const matches = this.findOccurrences(contents, oldString);
318
+ if (matches.length === 0) {
319
+ throw new Error("fs.edit: old_string not found");
320
+ }
321
+ if (!replaceAll && matches.length > 1) {
322
+ throw new Error("fs.edit: old_string appears multiple times");
323
+ }
324
+ const appliedMatches = replaceAll ? matches : matches.slice(0, 1);
325
+ const updated = replaceAll ? contents.split(oldString).join(newString) : contents.replace(oldString, newString);
326
+ await fs.writeFile(absPath, updated);
327
+ const lineSpec = this.formatLineRanges(
328
+ this.buildLineRanges(contents, appliedMatches, oldString.length)
329
+ );
330
+ return `Edited ${relPath} (replacements=${appliedMatches.length}, lines ${lineSpec})`;
331
+ }
265
332
  // ========================================================================
266
333
  // Path Safety
267
334
  // ========================================================================
@@ -541,6 +608,16 @@ var LocalFSToolPack = class {
541
608
  }
542
609
  return value;
543
610
  }
611
+ requireStringAllowEmpty(args, key, call) {
612
+ const value = args[key];
613
+ if (value === void 0 || value === null) {
614
+ this.toolArgumentError(call, `${key} is required`);
615
+ }
616
+ if (typeof value !== "string") {
617
+ this.toolArgumentError(call, `${key} must be a string`);
618
+ }
619
+ return value;
620
+ }
544
621
  optionalString(args, key, call) {
545
622
  const value = args[key];
546
623
  if (value === void 0 || value === null) {
@@ -568,6 +645,82 @@ var LocalFSToolPack = class {
568
645
  }
569
646
  return value;
570
647
  }
648
+ optionalBoolean(args, key, call) {
649
+ const value = args[key];
650
+ if (value === void 0 || value === null) {
651
+ return void 0;
652
+ }
653
+ if (typeof value !== "boolean") {
654
+ this.toolArgumentError(call, `${key} must be a boolean`);
655
+ }
656
+ return value;
657
+ }
658
+ findOccurrences(haystack, needle) {
659
+ if (needle.length === 0) {
660
+ return [];
661
+ }
662
+ const out = [];
663
+ let idx = 0;
664
+ for (; ; ) {
665
+ const found = haystack.indexOf(needle, idx);
666
+ if (found === -1) {
667
+ break;
668
+ }
669
+ out.push(found);
670
+ idx = found + needle.length;
671
+ }
672
+ return out;
673
+ }
674
+ buildLineRanges(source, offsets, needleLength) {
675
+ if (offsets.length === 0) {
676
+ return [];
677
+ }
678
+ const newlineOffsets = [];
679
+ for (let i = 0; i < source.length; i++) {
680
+ if (source.charCodeAt(i) === 10) {
681
+ newlineOffsets.push(i);
682
+ }
683
+ }
684
+ return offsets.map((startIdx) => {
685
+ const endIdx = startIdx + Math.max(needleLength, 1) - 1;
686
+ return {
687
+ start: this.lineNumberAt(newlineOffsets, startIdx),
688
+ end: this.lineNumberAt(newlineOffsets, endIdx)
689
+ };
690
+ });
691
+ }
692
+ lineNumberAt(newlineOffsets, index) {
693
+ let lo = 0;
694
+ let hi = newlineOffsets.length;
695
+ while (lo < hi) {
696
+ const mid = lo + hi >> 1;
697
+ if (newlineOffsets[mid] < index) {
698
+ lo = mid + 1;
699
+ } else {
700
+ hi = mid;
701
+ }
702
+ }
703
+ return lo + 1;
704
+ }
705
+ formatLineRanges(ranges) {
706
+ if (ranges.length === 0) {
707
+ return "";
708
+ }
709
+ const merged = [];
710
+ for (const range of ranges) {
711
+ const last = merged[merged.length - 1];
712
+ if (!last || range.start > last.end + 1) {
713
+ merged.push({ ...range });
714
+ continue;
715
+ }
716
+ if (range.end > last.end) {
717
+ last.end = range.end;
718
+ }
719
+ }
720
+ return merged.map(
721
+ (range) => range.start === range.end ? `${range.start}` : `${range.start}-${range.end}`
722
+ ).join(",");
723
+ }
571
724
  isValidUtf8(buffer) {
572
725
  try {
573
726
  const text = buffer.toString("utf-8");
@@ -272,10 +272,8 @@ interface FrontendToken {
272
272
  customerId: string;
273
273
  /** The external customer ID provided by the application. */
274
274
  customerExternalId: string;
275
- /** The tier code for the customer (e.g., "free", "pro", "enterprise").
276
- * Optional for BYOB (external billing) projects where customers may not have subscriptions.
277
- */
278
- tierCode?: string;
275
+ /** The tier code for the customer (e.g., "free", "pro", "enterprise"). */
276
+ tierCode: string;
279
277
  /**
280
278
  * Publishable key used for issuance. Added client-side for caching.
281
279
  */
@@ -306,8 +304,7 @@ interface CustomerToken {
306
304
  projectId: string;
307
305
  customerId: string;
308
306
  customerExternalId: string;
309
- /** Optional for BYOB (external billing) projects */
310
- tierCode?: string;
307
+ tierCode: string;
311
308
  }
312
309
  interface OIDCExchangeRequest {
313
310
  idToken: string;
@@ -359,8 +356,8 @@ interface DeviceTokenResponse {
359
356
  customerId: string;
360
357
  /** The external customer ID */
361
358
  customerExternalId: string;
362
- /** The tier code for the customer (optional for BYOB projects) */
363
- tierCode?: string;
359
+ /** The tier code for the customer */
360
+ tierCode: string;
364
361
  }
365
362
  /**
366
363
  * Pending response from polling device token endpoint.
@@ -706,7 +703,7 @@ interface APIFrontendToken {
706
703
  project_id: string;
707
704
  customer_id: string;
708
705
  customer_external_id: string;
709
- tier_code?: string;
706
+ tier_code: string;
710
707
  }
711
708
  interface APICustomerRef {
712
709
  id: string;
@@ -272,10 +272,8 @@ interface FrontendToken {
272
272
  customerId: string;
273
273
  /** The external customer ID provided by the application. */
274
274
  customerExternalId: string;
275
- /** The tier code for the customer (e.g., "free", "pro", "enterprise").
276
- * Optional for BYOB (external billing) projects where customers may not have subscriptions.
277
- */
278
- tierCode?: string;
275
+ /** The tier code for the customer (e.g., "free", "pro", "enterprise"). */
276
+ tierCode: string;
279
277
  /**
280
278
  * Publishable key used for issuance. Added client-side for caching.
281
279
  */
@@ -306,8 +304,7 @@ interface CustomerToken {
306
304
  projectId: string;
307
305
  customerId: string;
308
306
  customerExternalId: string;
309
- /** Optional for BYOB (external billing) projects */
310
- tierCode?: string;
307
+ tierCode: string;
311
308
  }
312
309
  interface OIDCExchangeRequest {
313
310
  idToken: string;
@@ -359,8 +356,8 @@ interface DeviceTokenResponse {
359
356
  customerId: string;
360
357
  /** The external customer ID */
361
358
  customerExternalId: string;
362
- /** The tier code for the customer (optional for BYOB projects) */
363
- tierCode?: string;
359
+ /** The tier code for the customer */
360
+ tierCode: string;
364
361
  }
365
362
  /**
366
363
  * Pending response from polling device token endpoint.
@@ -706,7 +703,7 @@ interface APIFrontendToken {
706
703
  project_id: string;
707
704
  customer_id: string;
708
705
  customer_external_id: string;
709
- tier_code?: string;
706
+ tier_code: string;
710
707
  }
711
708
  interface APICustomerRef {
712
709
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelrelay/sdk",
3
- "version": "4.0.0",
3
+ "version": "5.3.0",
4
4
  "description": "TypeScript SDK for the ModelRelay API",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",