copilot-sdk-supercharged 2.5.0 → 2.5.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: efac3a7b2a63cd85324b0b7c36d9ed46efdc5c2a792ab13dc4d707cc6f5e71a9
4
- data.tar.gz: 559699c4a862a0cbc494ecff5b5bdf5451f8f9e45800733264f937294a64055a
3
+ metadata.gz: f037082db29f02a659fb8872c0034e9917ea1542ba70dacf0fd608a741b34ab4
4
+ data.tar.gz: 5faaeffb467503b09766c52e1052b7dcf529821d8588343a0d47f76dfa347b6a
5
5
  SHA512:
6
- metadata.gz: 94b76e5263549a3df7d9f248f099bc9c98ac0ad66f8a2a05bb7a686a8c5c5519c67e045b2d228ed51f1e4570c3ce799bd34012cb3c8d118ad81fa6277312f3cd
7
- data.tar.gz: 38d704d797c11b42ad4ae921f25dd4bb98e3995fd58ddf36a0eaa3955f2361c6ab115ab41343ee2a1649445b600a086ae212278c61a6b4945d835296b736c00a
6
+ metadata.gz: 62a8d4f35acdfcae82332db0cc12cafa1db28e1aec8b877368fb7a9a5557caab6168646f8bc8eb87253ae07c35f93c85622978a520d5c7ab645a5229b5113974
7
+ data.tar.gz: 5866284c99f3107676ddd642184230d294a671745901319687b95f779ea22d60f1dff6d9b2410564ab3ec783278545811295722a7976e7ce5ccd2985c8f90a4e
@@ -5,6 +5,8 @@
5
5
  require "open3"
6
6
  require "socket"
7
7
  require "thread"
8
+ require "fileutils"
9
+ require "time"
8
10
 
9
11
  module Copilot
10
12
  # Main client for interacting with the Copilot CLI.
@@ -442,15 +444,18 @@ module Copilot
442
444
  #
443
445
  # @param initial_cwd [String, nil] Initial working directory
444
446
  # @param session_state_path [String, nil] Path for session state persistence
445
- # @param conventions [Array<String>, nil] Convention strings
447
+ # @param conventions [String, nil] Filesystem conventions ("posix" or "windows").
448
+ # Required by the CLI; defaults to the host platform's conventions when omitted.
446
449
  # @return [void]
447
450
  def set_session_fs_provider(initial_cwd: nil, session_state_path: nil, conventions: nil)
448
451
  raise_not_connected! unless @rpc_client
449
452
 
453
+ conventions ||= (RUBY_PLATFORM =~ /mswin|mingw|cygwin/ ? "windows" : "posix")
454
+
450
455
  params = {}
451
456
  params[:initialCwd] = initial_cwd if initial_cwd
452
457
  params[:sessionStatePath] = session_state_path if session_state_path
453
- params[:conventions] = conventions if conventions
458
+ params[:conventions] = conventions
454
459
 
455
460
  @rpc_client.request("sessionFs.setProvider", params)
456
461
  end
@@ -647,6 +652,29 @@ module Copilot
647
652
  @rpc_client.on_request("exitPlanMode.request") do |params|
648
653
  handle_exit_plan_mode_request(params)
649
654
  end
655
+
656
+ register_session_fs_handlers
657
+ end
658
+
659
+ # Registers the client-side session filesystem request handlers.
660
+ #
661
+ # When a session filesystem provider is set via {#set_session_fs_provider},
662
+ # the CLI drives filesystem access by sending +sessionFs.*+ requests back to
663
+ # the client. These handlers service those requests against the real
664
+ # filesystem using the absolute paths supplied by the CLI. Errors are
665
+ # returned in-band as +{ code:, message: }+ (SessionFsError) rather than
666
+ # raised, matching the reference SDK contract.
667
+ def register_session_fs_handlers
668
+ @rpc_client.on_request("sessionFs.readFile") { |params| handle_session_fs_read_file(params) }
669
+ @rpc_client.on_request("sessionFs.writeFile") { |params| handle_session_fs_write_file(params) }
670
+ @rpc_client.on_request("sessionFs.appendFile") { |params| handle_session_fs_append_file(params) }
671
+ @rpc_client.on_request("sessionFs.exists") { |params| handle_session_fs_exists(params) }
672
+ @rpc_client.on_request("sessionFs.stat") { |params| handle_session_fs_stat(params) }
673
+ @rpc_client.on_request("sessionFs.mkdir") { |params| handle_session_fs_mkdir(params) }
674
+ @rpc_client.on_request("sessionFs.readdir") { |params| handle_session_fs_readdir(params) }
675
+ @rpc_client.on_request("sessionFs.readdirWithTypes") { |params| handle_session_fs_readdir_with_types(params) }
676
+ @rpc_client.on_request("sessionFs.rm") { |params| handle_session_fs_rm(params) }
677
+ @rpc_client.on_request("sessionFs.rename") { |params| handle_session_fs_rename(params) }
650
678
  end
651
679
 
652
680
  def verify_protocol_version
@@ -798,6 +826,135 @@ module Copilot
798
826
  end
799
827
  end
800
828
 
829
+ # ---- SessionFs provider request handlers ----
830
+ #
831
+ # These service the CLI's +sessionFs.*+ callbacks against the real
832
+ # filesystem. Success returns mirror the generated result shapes and errors
833
+ # are returned in-band as a SessionFsError (+{ code:, message: }+) so the CLI
834
+ # can distinguish a missing file (ENOENT) from other failures without the
835
+ # request failing at the JSON-RPC layer.
836
+
837
+ def handle_session_fs_read_file(params)
838
+ content = File.read(params["path"], encoding: "UTF-8")
839
+ { content: content }
840
+ rescue StandardError => e
841
+ { content: "", error: session_fs_error(e) }
842
+ end
843
+
844
+ def handle_session_fs_write_file(params)
845
+ path = params["path"]
846
+ FileUtils.mkdir_p(File.dirname(path))
847
+ File.write(path, params["content"].to_s)
848
+ File.chmod(params["mode"], path) if params["mode"]
849
+ nil
850
+ rescue StandardError => e
851
+ session_fs_error(e)
852
+ end
853
+
854
+ def handle_session_fs_append_file(params)
855
+ path = params["path"]
856
+ FileUtils.mkdir_p(File.dirname(path))
857
+ File.open(path, "a") { |f| f.write(params["content"].to_s) }
858
+ File.chmod(params["mode"], path) if params["mode"]
859
+ nil
860
+ rescue StandardError => e
861
+ session_fs_error(e)
862
+ end
863
+
864
+ def handle_session_fs_exists(params)
865
+ { exists: File.exist?(params["path"]) }
866
+ rescue StandardError
867
+ { exists: false }
868
+ end
869
+
870
+ def handle_session_fs_stat(params)
871
+ st = File.stat(params["path"])
872
+ {
873
+ isFile: st.file?,
874
+ isDirectory: st.directory?,
875
+ size: st.size,
876
+ mtime: st.mtime.utc.iso8601,
877
+ birthtime: session_fs_birthtime(st).utc.iso8601,
878
+ }
879
+ rescue StandardError => e
880
+ now = Time.now.utc.iso8601
881
+ {
882
+ isFile: false,
883
+ isDirectory: false,
884
+ size: 0,
885
+ mtime: now,
886
+ birthtime: now,
887
+ error: session_fs_error(e),
888
+ }
889
+ end
890
+
891
+ def handle_session_fs_mkdir(params)
892
+ path = params["path"]
893
+ if params["recursive"]
894
+ FileUtils.mkdir_p(path)
895
+ else
896
+ Dir.mkdir(path)
897
+ end
898
+ File.chmod(params["mode"], path) if params["mode"]
899
+ nil
900
+ rescue StandardError => e
901
+ session_fs_error(e)
902
+ end
903
+
904
+ def handle_session_fs_readdir(params)
905
+ { entries: Dir.children(params["path"]) }
906
+ rescue StandardError => e
907
+ { entries: [], error: session_fs_error(e) }
908
+ end
909
+
910
+ def handle_session_fs_readdir_with_types(params)
911
+ path = params["path"]
912
+ entries = Dir.children(path).map do |name|
913
+ { name: name, type: File.directory?(File.join(path, name)) ? "directory" : "file" }
914
+ end
915
+ { entries: entries }
916
+ rescue StandardError => e
917
+ { entries: [], error: session_fs_error(e) }
918
+ end
919
+
920
+ def handle_session_fs_rm(params)
921
+ path = params["path"]
922
+ force = params["force"]
923
+ if params["recursive"]
924
+ force ? FileUtils.rm_rf(path) : FileUtils.rm_r(path)
925
+ elsif force
926
+ FileUtils.rm_f(path)
927
+ else
928
+ File.delete(path)
929
+ end
930
+ nil
931
+ rescue Errno::ENOENT => e
932
+ params["force"] ? nil : session_fs_error(e)
933
+ rescue StandardError => e
934
+ session_fs_error(e)
935
+ end
936
+
937
+ def handle_session_fs_rename(params)
938
+ File.rename(params["src"], params["dest"])
939
+ nil
940
+ rescue StandardError => e
941
+ session_fs_error(e)
942
+ end
943
+
944
+ # Returns the creation time when the platform supports it, else falls back to mtime.
945
+ def session_fs_birthtime(stat)
946
+ stat.birthtime
947
+ rescue NotImplementedError
948
+ stat.mtime
949
+ end
950
+
951
+ # Maps a Ruby exception to a SessionFsError hash. Missing paths map to
952
+ # "ENOENT"; everything else maps to "UNKNOWN", matching the reference SDK.
953
+ def session_fs_error(err)
954
+ code = err.is_a?(Errno::ENOENT) ? "ENOENT" : "UNKNOWN"
955
+ { code: code, message: err.message }
956
+ end
957
+
801
958
  # ---- Payload builders ----
802
959
 
803
960
  def build_create_session_payload(config)
@@ -296,7 +296,11 @@ module Copilot
296
296
  # Execute handler (may be synchronous or may raise)
297
297
  begin
298
298
  result = handler.call(params)
299
- result = {} if result.nil?
299
+ # sessionFs.* write-ops (writeFile/appendFile/mkdir/rm/rename) signal success
300
+ # as JSON null (the CLI decodes the result as `SessionFsError | undefined`).
301
+ # Coercing nil to {} would be decoded as a malformed SessionFsError
302
+ # ("missing field `code`"), so preserve nil for those methods only.
303
+ result = {} if result.nil? && !method.to_s.start_with?("sessionFs.")
300
304
  send_response(request_id, result)
301
305
  rescue JsonRpcError => e
302
306
  send_error_response(request_id, e.code, e.message, e.data)
data/lib/copilot/types.rb CHANGED
@@ -599,6 +599,7 @@ module Copilot
599
599
  :session_fs,
600
600
  :copilot_home, :tcp_connection_token,
601
601
  :request_handler,
602
+ :on_get_trace_context,
602
603
  keyword_init: true
603
604
  )
604
605
 
@@ -3,5 +3,5 @@
3
3
  # Copyright (c) Microsoft Corporation. All rights reserved.
4
4
 
5
5
  module Copilot
6
- VERSION = "2.5.0"
6
+ VERSION = "2.5.2"
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: copilot-sdk-supercharged
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.0
4
+ version: 2.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremiah Isaacson