|
22 | 22 | import com.github.copilot.rpc.CopilotClientMode; |
23 | 23 | import com.github.copilot.rpc.CopilotClientOptions; |
24 | 24 | import com.github.copilot.rpc.CreateSessionResponse; |
| 25 | +import com.github.copilot.generated.rpc.SessionOptionsUpdateParams; |
| 26 | +import com.github.copilot.generated.rpc.SessionInstalledPlugin; |
25 | 27 | import com.github.copilot.generated.rpc.ConnectParams; |
26 | 28 | import com.github.copilot.generated.rpc.ServerRpc; |
27 | 29 | import com.github.copilot.rpc.DeleteSessionResponse; |
@@ -484,30 +486,40 @@ public CompletableFuture<CopilotSession> createSession(SessionConfig config) { |
484 | 486 | } |
485 | 487 |
|
486 | 488 | long rpcNanos = System.nanoTime(); |
487 | | - return connection.rpc.invoke("session.create", request, CreateSessionResponse.class).thenApply(response -> { |
488 | | - LoggingHelpers.logTiming(LOG, Level.FINE, |
489 | | - "CopilotClient.createSession session creation request completed. Elapsed={Elapsed}, SessionId=" |
490 | | - + sessionId, |
491 | | - rpcNanos); |
492 | | - session.setWorkspacePath(response.workspacePath()); |
493 | | - session.setCapabilities(response.capabilities()); |
494 | | - // If the server returned a different sessionId (e.g. a v2 CLI that ignores |
495 | | - // the client-supplied ID), re-key the sessions map. |
496 | | - String returnedId = response.sessionId(); |
497 | | - if (returnedId != null && !returnedId.equals(sessionId)) { |
498 | | - sessions.remove(sessionId); |
499 | | - session.setActiveSessionId(returnedId); |
500 | | - sessions.put(returnedId, session); |
501 | | - } |
502 | | - LoggingHelpers.logTiming(LOG, Level.FINE, |
503 | | - "CopilotClient.createSession complete. Elapsed={Elapsed}, SessionId=" + sessionId, totalNanos); |
504 | | - return session; |
505 | | - }).exceptionally(ex -> { |
506 | | - sessions.remove(sessionId); |
507 | | - LoggingHelpers.logTiming(LOG, Level.WARNING, ex, |
508 | | - "CopilotClient.createSession failed. Elapsed={Elapsed}, SessionId=" + sessionId, totalNanos); |
509 | | - throw ex instanceof RuntimeException re ? re : new RuntimeException(ex); |
510 | | - }); |
| 489 | + return connection.rpc.invoke("session.create", request, CreateSessionResponse.class) |
| 490 | + .thenCompose(response -> { |
| 491 | + LoggingHelpers.logTiming(LOG, Level.FINE, |
| 492 | + "CopilotClient.createSession session creation request completed. Elapsed={Elapsed}, SessionId=" |
| 493 | + + sessionId, |
| 494 | + rpcNanos); |
| 495 | + session.setWorkspacePath(response.workspacePath()); |
| 496 | + session.setCapabilities(response.capabilities()); |
| 497 | + // If the server returned a different sessionId (e.g. a v2 CLI that ignores |
| 498 | + // the client-supplied ID), re-key the sessions map. |
| 499 | + String returnedId = response.sessionId(); |
| 500 | + if (returnedId != null && !returnedId.equals(sessionId)) { |
| 501 | + sessions.remove(sessionId); |
| 502 | + session.setActiveSessionId(returnedId); |
| 503 | + sessions.put(returnedId, session); |
| 504 | + } |
| 505 | + |
| 506 | + return updateSessionOptionsForMode(session, config.getSkipCustomInstructions().orElse(null), |
| 507 | + config.getCustomAgentsLocalOnly().orElse(null), |
| 508 | + config.getCoauthorEnabled().orElse(null), |
| 509 | + config.getManageScheduleEnabled().orElse(null)).thenApply(v -> { |
| 510 | + LoggingHelpers.logTiming(LOG, Level.FINE, |
| 511 | + "CopilotClient.createSession complete. Elapsed={Elapsed}, SessionId=" |
| 512 | + + sessionId, |
| 513 | + totalNanos); |
| 514 | + return session; |
| 515 | + }); |
| 516 | + }).exceptionally(ex -> { |
| 517 | + sessions.remove(sessionId); |
| 518 | + LoggingHelpers.logTiming(LOG, Level.WARNING, ex, |
| 519 | + "CopilotClient.createSession failed. Elapsed={Elapsed}, SessionId=" + sessionId, |
| 520 | + totalNanos); |
| 521 | + throw ex instanceof RuntimeException re ? re : new RuntimeException(ex); |
| 522 | + }); |
511 | 523 | }); |
512 | 524 | } |
513 | 525 |
|
@@ -581,29 +593,158 @@ public CompletableFuture<CopilotSession> resumeSession(String sessionId, ResumeS |
581 | 593 | } |
582 | 594 |
|
583 | 595 | long rpcNanos = System.nanoTime(); |
584 | | - return connection.rpc.invoke("session.resume", request, ResumeSessionResponse.class).thenApply(response -> { |
585 | | - LoggingHelpers.logTiming(LOG, Level.FINE, |
586 | | - "CopilotClient.resumeSession session resume request completed. Elapsed={Elapsed}, SessionId=" |
587 | | - + sessionId, |
588 | | - rpcNanos); |
589 | | - session.setWorkspacePath(response.workspacePath()); |
590 | | - session.setCapabilities(response.capabilities()); |
591 | | - // If the server returned a different sessionId than what was requested, re-key. |
592 | | - String returnedId = response.sessionId(); |
593 | | - if (returnedId != null && !returnedId.equals(sessionId)) { |
594 | | - sessions.remove(sessionId); |
595 | | - session.setActiveSessionId(returnedId); |
596 | | - sessions.put(returnedId, session); |
597 | | - } |
598 | | - LoggingHelpers.logTiming(LOG, Level.FINE, |
599 | | - "CopilotClient.resumeSession complete. Elapsed={Elapsed}, SessionId=" + sessionId, totalNanos); |
600 | | - return session; |
601 | | - }).exceptionally(ex -> { |
602 | | - sessions.remove(sessionId); |
603 | | - LoggingHelpers.logTiming(LOG, Level.WARNING, ex, |
604 | | - "CopilotClient.resumeSession failed. Elapsed={Elapsed}, SessionId=" + sessionId, totalNanos); |
605 | | - throw ex instanceof RuntimeException re ? re : new RuntimeException(ex); |
606 | | - }); |
| 596 | + return connection.rpc.invoke("session.resume", request, ResumeSessionResponse.class) |
| 597 | + .thenCompose(response -> { |
| 598 | + LoggingHelpers.logTiming(LOG, Level.FINE, |
| 599 | + "CopilotClient.resumeSession session resume request completed. Elapsed={Elapsed}, SessionId=" |
| 600 | + + sessionId, |
| 601 | + rpcNanos); |
| 602 | + session.setWorkspacePath(response.workspacePath()); |
| 603 | + session.setCapabilities(response.capabilities()); |
| 604 | + // If the server returned a different sessionId than what was requested, |
| 605 | + // re-key. |
| 606 | + String returnedId = response.sessionId(); |
| 607 | + if (returnedId != null && !returnedId.equals(sessionId)) { |
| 608 | + sessions.remove(sessionId); |
| 609 | + session.setActiveSessionId(returnedId); |
| 610 | + sessions.put(returnedId, session); |
| 611 | + } |
| 612 | + |
| 613 | + return updateSessionOptionsForMode(session, config.getSkipCustomInstructions().orElse(null), |
| 614 | + config.getCustomAgentsLocalOnly().orElse(null), |
| 615 | + config.getCoauthorEnabled().orElse(null), |
| 616 | + config.getManageScheduleEnabled().orElse(null)).thenApply(v -> { |
| 617 | + LoggingHelpers.logTiming(LOG, Level.FINE, |
| 618 | + "CopilotClient.resumeSession complete. Elapsed={Elapsed}, SessionId=" |
| 619 | + + sessionId, |
| 620 | + totalNanos); |
| 621 | + return session; |
| 622 | + }); |
| 623 | + }).exceptionally(ex -> { |
| 624 | + sessions.remove(sessionId); |
| 625 | + LoggingHelpers.logTiming(LOG, Level.WARNING, ex, |
| 626 | + "CopilotClient.resumeSession failed. Elapsed={Elapsed}, SessionId=" + sessionId, |
| 627 | + totalNanos); |
| 628 | + throw ex instanceof RuntimeException re ? re : new RuntimeException(ex); |
| 629 | + }); |
| 630 | + }); |
| 631 | + } |
| 632 | + |
| 633 | + /** |
| 634 | + * Applies the post-create / post-resume {@code session.options.update} patch. |
| 635 | + * <p> |
| 636 | + * In {@link CopilotClientMode#EMPTY EMPTY} mode this defaults the four |
| 637 | + * overridable feature flags to safe values (caller values from the config win); |
| 638 | + * {@code installedPlugins=[]} is unconditional under empty mode so apps that |
| 639 | + * need plugins must switch modes. In {@link CopilotClientMode#COPILOT_CLI |
| 640 | + * COPILOT_CLI} mode only explicitly-set fields are forwarded. |
| 641 | + * |
| 642 | + * @param session |
| 643 | + * the session to patch |
| 644 | + * @param skipCustomInstructions |
| 645 | + * caller-supplied value, or {@code null} if not set |
| 646 | + * @param customAgentsLocalOnly |
| 647 | + * caller-supplied value, or {@code null} if not set |
| 648 | + * @param coauthorEnabled |
| 649 | + * caller-supplied value, or {@code null} if not set |
| 650 | + * @param manageScheduleEnabled |
| 651 | + * caller-supplied value, or {@code null} if not set |
| 652 | + * @return a future that completes when the patch has been applied |
| 653 | + */ |
| 654 | + CompletableFuture<Void> updateSessionOptionsForMode(CopilotSession session, Boolean skipCustomInstructions, |
| 655 | + Boolean customAgentsLocalOnly, Boolean coauthorEnabled, Boolean manageScheduleEnabled) { |
| 656 | + |
| 657 | + Boolean patchSkip = null; |
| 658 | + Boolean patchAgents = null; |
| 659 | + Boolean patchCoauthor = null; |
| 660 | + Boolean patchSchedule = null; |
| 661 | + List<SessionInstalledPlugin> patchPlugins = null; |
| 662 | + boolean hasAnyPatch = false; |
| 663 | + |
| 664 | + if (options.getMode() == CopilotClientMode.EMPTY) { |
| 665 | + patchSkip = skipCustomInstructions != null ? skipCustomInstructions : true; |
| 666 | + patchAgents = customAgentsLocalOnly != null ? customAgentsLocalOnly : true; |
| 667 | + patchCoauthor = coauthorEnabled != null ? coauthorEnabled : false; |
| 668 | + patchSchedule = manageScheduleEnabled != null ? manageScheduleEnabled : false; |
| 669 | + patchPlugins = List.of(); |
| 670 | + hasAnyPatch = true; |
| 671 | + } else { |
| 672 | + if (skipCustomInstructions != null) { |
| 673 | + patchSkip = skipCustomInstructions; |
| 674 | + hasAnyPatch = true; |
| 675 | + } |
| 676 | + if (customAgentsLocalOnly != null) { |
| 677 | + patchAgents = customAgentsLocalOnly; |
| 678 | + hasAnyPatch = true; |
| 679 | + } |
| 680 | + if (coauthorEnabled != null) { |
| 681 | + patchCoauthor = coauthorEnabled; |
| 682 | + hasAnyPatch = true; |
| 683 | + } |
| 684 | + if (manageScheduleEnabled != null) { |
| 685 | + patchSchedule = manageScheduleEnabled; |
| 686 | + hasAnyPatch = true; |
| 687 | + } |
| 688 | + } |
| 689 | + |
| 690 | + if (!hasAnyPatch) { |
| 691 | + return CompletableFuture.completedFuture(null); |
| 692 | + } |
| 693 | + |
| 694 | + var params = new SessionOptionsUpdateParams(null, // sessionId — set by SessionOptionsApi |
| 695 | + null, // model |
| 696 | + null, // reasoningEffort |
| 697 | + null, // clientName |
| 698 | + null, // lspClientName |
| 699 | + null, // integrationId |
| 700 | + null, // featureFlags |
| 701 | + null, // isExperimentalMode |
| 702 | + null, // provider |
| 703 | + null, // workingDirectory |
| 704 | + null, // availableTools |
| 705 | + null, // excludedTools |
| 706 | + null, // toolFilterPrecedence |
| 707 | + null, // enableScriptSafety |
| 708 | + null, // shellInitProfile |
| 709 | + null, // shellProcessFlags |
| 710 | + null, // sandboxConfig |
| 711 | + null, // logInteractiveShells |
| 712 | + null, // envValueMode |
| 713 | + null, // skillDirectories |
| 714 | + null, // disabledSkills |
| 715 | + null, // enableOnDemandInstructionDiscovery |
| 716 | + patchPlugins, // installedPlugins |
| 717 | + patchAgents, // customAgentsLocalOnly |
| 718 | + patchSkip, // skipCustomInstructions |
| 719 | + null, // disabledInstructionSources |
| 720 | + patchCoauthor, // coauthorEnabled |
| 721 | + null, // trajectoryFile |
| 722 | + null, // enableStreaming |
| 723 | + null, // copilotUrl |
| 724 | + null, // askUserDisabled |
| 725 | + null, // continueOnAutoMode |
| 726 | + null, // runningInInteractiveMode |
| 727 | + null, // enableReasoningSummaries |
| 728 | + null, // agentContext |
| 729 | + null, // eventsLogDirectory |
| 730 | + null, // additionalContentExclusionPolicies |
| 731 | + patchSchedule // manageScheduleEnabled |
| 732 | + ); |
| 733 | + |
| 734 | + return session.getRpc().options.update(params).<Void>thenCompose(result -> { |
| 735 | + LOG.fine("session.options.update applied for session " + session.getSessionId()); |
| 736 | + return CompletableFuture.completedFuture(null); |
| 737 | + }).exceptionally(ex -> { |
| 738 | + // The runtime session exists but the post-create options patch failed. |
| 739 | + // Best-effort disconnect so we don't leak it (in empty mode it would |
| 740 | + // otherwise stay alive with permissive defaults). |
| 741 | + LOG.log(Level.WARNING, "session.options.update failed for session " + session.getSessionId(), ex); |
| 742 | + try { |
| 743 | + session.close(); |
| 744 | + } catch (Exception closeEx) { |
| 745 | + // Swallow: original error is the one the caller needs. |
| 746 | + } |
| 747 | + throw ex instanceof RuntimeException re ? re : new RuntimeException(ex); |
607 | 748 | }); |
608 | 749 | } |
609 | 750 |
|
|
0 commit comments