Skip to content

Main dev - #658

Closed
JayDaniel wants to merge 16 commits into
intitni:mainfrom
JayDaniel:main-dev
Closed

Main dev#658
JayDaniel wants to merge 16 commits into
intitni:mainfrom
JayDaniel:main-dev

Conversation

@JayDaniel

Copy link
Copy Markdown

No description provided.

TCA 1.16.1 does not compile under Swift 6.3, so moving to the new
toolchain forces a matching dependency bump.

- Upgrade swift-composable-architecture from 1.16.1 to 1.26.2 and
  adopt the new Shared(value:) signature
- Align the Tool and ChatPlugins package platforms to macOS 13, so
  they match every target in the project
- Declare the missing CommandHandler dependency in SuggestionWidget,
  which was breaking the test bundle link step
- Drop stale entries from Package.resolved

Claude-Session: https://claude.ai/code/session_01QrUJLx9ppwpa2DFUoSGUYo
The open source build hosts no third-party ExtensionKit extension, so
CustomSuggestionServiceForCopilotForXcode cannot run as a separate
app. Merge it into the Tool package and expose it as a built-in
extension instead.

- Add the CustomSuggestionService module to the Tool package
- Support seven API shapes: OpenAI compatible, Azure, Claude, Gemini,
  Ollama, Mistral FIM and Tabby
- Add the CompletionModel, FIMModel and TabbyModel types along with
  the customSuggestion* preference keys
- Register through BuiltinExtension, so that Custom Model appears as
  a suggestion provider
- Port the 31 unit tests from the original project

Claude-Session: https://claude.ai/code/session_01QrUJLx9ppwpa2DFUoSGUYo
- Replace the Other Suggestion Services promo page in the Service
  sidebar with a Custom Model settings page
- Let the user pick a host chat model, or a custom Completion, FIM or
  Tabby model, and edit it in place
- Expose the request strategy, the FIM template, the line and token
  limits, and the verbose log switch
- Give APIKeyPicker an optional title

Claude-Session: https://claude.ai/code/session_01QrUJLx9ppwpa2DFUoSGUYo
Replace the upstream author's team in all 12 DEVELOPMENT_TEAM build
settings, so that the fork builds on this machine.

Claude-Session: https://claude.ai/code/session_01QrUJLx9ppwpa2DFUoSGUYo
- Add OpenAIResponsesService, covering both the streaming and the
  non-streaming shape of /v1/responses
- Switch OpenAIService to the reasoning request shape once an effort
  is set
- Forward the host chat model's custom headers through the OpenAI and
  the Anthropic service
- Add a Chat Model API section to the settings page for choosing the
  endpoint and the reasoning effort
- Cover SSE parsing and header placeholders with unit tests

Claude-Session: https://claude.ai/code/session_01QrUJLx9ppwpa2DFUoSGUYo
A debug build could not reach its services until the launch agent was
installed by hand, and the host insisted on being moved to
/Applications first.

- Add an idempotent debug-only registration path: write the plist,
  bootstrap it, and kickstart the bridge once it is rebuilt
- Register on host launch in debug, and drop the move to Applications
  prompt there
- Let the bridge start ExtensionService in debug as well
- Have ExtensionService exit in debug once its binary changes, so it
  comes back running the new code
- Leave release behaviour identical to upstream

Claude-Session: https://claude.ai/code/session_01QrUJLx9ppwpa2DFUoSGUYo
- Point Package.resolved at apple/swift-syntax.git, to match
  Tool/Package.swift
- Set all 12 DEVELOPMENT_TEAM settings to $(inherited) and read the
  real value from an optional Local.xcconfig
- Add Local.xcconfig.example and gitignore Local.xcconfig
- Drop the unused CodableWrappers dependency, and the unused Parsing
  import in RequestStrategy
Completion failures used to disappear into an erased stream, so a
misconfigured model looked exactly like a model with nothing to say.

- Drop eraseToStream and let getCompletion throw; Tabby and Gemini
  now use AsyncThrowingStream
- Parse a non-SSE response as one JSON body, report a non-200 as
  "HTTP <code>: <first 200 bytes>", and stop turning a response that
  is not an HTTPURLResponse into a CancellationError
- Raise readable errors from the error.message line of
  chat/completions and from Anthropic type=error events
- Recognise the chat shape {"error":{"message":...}} before switching
  on the Responses event type, which used to hang
- Stop silently falling back on a dangling model id, clear
  customSuggestionModelId when its chat model is deleted, and give an
  empty id its own message
- Rate-limit repeated completion errors to silent within 30 seconds
  and notice otherwise; the presenter skips silent ones, and
  cancellation, URLError.cancelled included, is no longer logged as
  an error
- Map Completions chunks onto chat chunks, so the OpenAI completion
  path can share CompletionSequence
- Keep parsing Ollama application/x-ndjson line by line, since
  streaming NDJSON is not a single JSON document

The Gemini Google SDK session is not intercepted, so its tests cover
the same AsyncThrowingStream wrapper instead. Toasting a wrong API
key was not exercised by hand, this machine having no GUI for it.
- Move OpenAIChatAPI and ReasoningEffort into Preferences, key the API
  options by ChatModel.id, xhigh and the reasoning token budget
  included, and migrate the old global keys once
- Treat a Responses endpoint as a full URL only for openAICompatible
  with isFullURL; strip the trailing slash and ignore the query before
  swapping /chat/completions for /responses
- Always truncate on stop words client side; with max lines at 0, stop
  words become the only limit. Add the budget to max_*_tokens for low,
  medium, high and xhigh
- Send store:false on Responses requests, and temperature:0.2 when no
  effort is set; send output_config.effort to Anthropic, mapping none
  and minimal to low, and xhigh to high
- Route headers through HeaderValueParser, validating names and
  newlines; forward extraHeaders to Azure and Ollama; merge customBody
  with JoinJSON; align org and project, the Gemini base URL and the
  message order with the host
- Show GitHub Copilot models in the picker as needing another choice,
  and throw from the completion path
- Show the reasoning token budget only for OpenAI and compatible

CustomSuggestionServiceTests passes with 54 cases. The gateway itself
was not verified by hand, this machine having neither a GUI nor
gateway credentials.
- Fingerprint a debug build by the mtime and size of
  <executable>.debug.dylib and quit on change; release keeps the
  FileChangeChecker MD5
- Skip bridge.quit() from XPCController.quit() in debug, which used to
  trip launchd's ten second throttle
- Return an endpoint only for a live PID, and clear the endpoint and
  PID when the liveness check fails, rather than holding a stale
  handle because a bundle id was reused; ping every 5 seconds in
  debug and every 60 in release
- Reuse an instance from launch() only when its bundleURL and appURL
  normalise to the same path, and go through openApplication otherwise
- Build the plist with PropertyListSerialization and compare parsed
  dictionaries for idempotency; mention login items only when stderr
  reports an input/output error or a disabled agent
- Make setupLaunchAgentIfNeeded cancellable, run the automatic
  registration once per host process, always re-register from the
  Setup button, and clear didRunInThisProcess on Remove
- Log LaunchAgentManager under Bundle.main.bundleIdentifier

Add LaunchAgentManagerTests, covering a Program path containing an
ampersand. The three manual checks, pid change after a rebuild,
kickstart and single instance, were not run, this machine having no
GUI.
- Write back only the model fields when the edit sheet reads from
  disk, instead of rebuilding the API key and base URL pickers, which
  raced with an empty tag
- Give BuiltInSuggestionFeatureProvider.customModel the raw value
  1000. A persisted SuggestionFeatureProvider of Int 2 now fails to
  decode and falls back to the default provider, so the user has to
  pick Custom Model once
- Keep the key names of the standalone app without migrating its
  configuration, and treat the old chatModel value as an invalid model
- Snapshot modelId, chatModels and strategyId once in the coordinator,
  save through userDefaults behind a non-empty check, and forward
  cancellation to the inner task
- Populate the Completion picker from the completions list and the FIM
  picker from the codestral list, and drop the unreachable googleAI
  view
- Fold the three model field views into SharedModelFields, and move
  RequestStrategyOption and the model lists out of the engine, so
  HostApp no longer depends on CustomSuggestionService

Tests cover saving from CompletionModelEdit and the raw 1000 round
trip for SuggestionFeatureProvider. The sheet pickers were not
verified in the GUI.
- Give OpenAIService an authentication mode, bearer or api-key, and an
  Azure endpoint builder; route .azureOpenAI through it and delete
  AzureOpenAIService
- The Azure path now inherits SSELine, custom headers, the reasoning
  shape with max_completion_tokens, and non-streaming parsing
- Delete the unused APIError, decodeError and countToken from the
  OpenAI and Ollama services, and Models and validateResponse from
  Anthropic
- Log CompletionModel under the Completion type, and fall back to
  http://127.0.0.1:11434 for Ollama
- Show the reasoning effort and token budget on the Azure settings
  page, move the chat api-version to 2024-09-01-preview, and fail on
  an empty Azure URL rather than calling api.openai.com

Tests assert that an Azure reasoning request carries api-key,
max_completion_tokens, /openai/deployments and api-version.
CustomSuggestionServiceTests passes with 56 cases.
- Set a same-team code signing requirement on the CommunicationBridge
  and ExtensionService listeners before resume, and refuse the
  connection when the team id is unreadable or the requirement invalid
- Derive the team id by dropping the trailing dot from
  TEAM_ID_PREFIX; the bridge reads it from the Info.plist of the
  neighbouring CopilotForXcodeExtensionService.app
- Accept updateServiceEndpoint only from a process whose bundle id is
  <base>.ExtensionService, and log the rest
- Say in the verbose log switch that it writes prompts containing your
  source code to Console.app
- Log the requirement string on refusal, rather than a localized
  description that is null

A signed debug build passes with TEAM_ID_PREFIX=94G4SCKS9Z. The
version row on the General page, and the refusal of a client from
another team, were not verified in the GUI.
- README: list Custom Model under the features, require macOS 13, and
  document the Service > Chat Models > Custom Model > Feature >
  Suggestion setup, the gateway headers that are not forwarded
  upstream, and the MIT provenance
- DEVELOPMENT: running Copilot for Xcode Debug in the fork now starts
  the host, the bridge and ExtensionService. The launch agent owns
  CommunicationBridge, so neither that scheme nor ExtensionService
  should be run directly; keep one dev instance at a time; the .appex
  still follows Apple's EditorExtension flow; debug by attaching to
  the process
- Document Local.xcconfig under Building: copy DEVELOPMENT_TEAM from
  the example, it is gitignored
- Close the remaining gaps: ChatAPIOptions.current for the default,
  an invalid raw value and an empty key; getModel and getStrategy,
  a dangling id included; parseCompletionsStreamLine; and
  response.incomplete

Optionally run the tests on pull_request under macos-15 with Xcode 26
and CODE_SIGNING_ALLOWED=NO. The test step stays continue-on-error
while Xcode 26 is unavailable.

The full run passes with 272 cases: 63 in CustomSuggestionServiceTests,
5 in HostAppTests and 2 in LaunchAgentManagerTests.
Six changes land together, all of them about whether custom model
completion is pleasant to use in practice.

- Accept on Tab the way Cursor does: if a suggestion is showing, Tab
  takes it. The upstream heuristic of letting Tab through when it
  would not invalidate the suggestion turned one Tab into three
  whenever a continuation began with whitespace. Deciding this no
  longer reads the editor content either.
- Strip duplicated indentation in post-processing: when the prefix
  before the cursor ends in whitespace, drop the leading spaces and
  tabs from the continuation.
- Stream the suggestion as it arrives. Add
  StreamingSuggestionServiceProvider and
  StreamingSuggestionServiceType, turn CodeCompletionService into a
  type-erasing factory offering streamCompletion, have the coordinator
  emit a partial result every 50ms, and refresh the widget on each
  one. Accepting, rejecting or invalidating a suggestion stops and
  cancels the request at once. The log gains first token, partial
  count, output tokens and the endpoint actually called.
- Retrieve context. relevantCodeSnippets had always been an empty
  array, which is why completions felt unrelated to the project.
  NeighboringSnippetRetriever scores windows of the other open files
  by Jaccard similarity, the way GitHub Copilot uses neighbouring
  tabs, with no index, no embeddings and no extra round trip.
  RecentEditDiff records the last few edits and renders them as a
  fully commented block, the signal Cursor Tab leans on; commenting
  it keeps the syntax intact inside a FIM prefix. Candidates come
  from the filespaces, topped up from the persisted open file list
  when the service has just restarted.
- Default the OpenAI endpoint to /v1/responses. The old global key
  joins the migration whenever it holds a value, so a configuration
  that explicitly chose chat completions is not overwritten by the
  new default.
- Stop reporting Plus limits in an unlicensed build: the model and
  custom command counts are no longer capped. Features implemented in
  a closed source module stay guarded by canImport at the call site,
  the browser tab among them.

Tests: 21 new cases for neighbouring tab retrieval and recent edits,
plus streaming and post-processing cases, 300 passing in total. Four
WebSearchServiceTests cases need the public network and pass on their
own.

Claude-Session: https://claude.ai/code/session_01JWdMNzGfXMq5ptxmB4Vmgs
SwiftUI resolves a Text literal against Bundle.main, so localizations
shipped inside an SPM package never apply. Putting the catalog in the
three executable bundles instead localizes roughly 550 call sites
across the Core and Tool packages without touching any of them.

- Add Localization/Localizable.xcstrings with 450 entries in en,
  zh-Hans and zh-Hant, and attach it to the host app, the extension
  service and the editor extension
- Narrow the title of SettingsDivider, SubSection, SidebarTabView,
  TabContainer and the pickers from String to LocalizedStringKey, so
  that their call sites need no edit
- Look toast text up as a LocalizedStringKey, and run the status bar
  menu and the Xcode editor command names through NSLocalizedString
- Add AppLanguage and the appLanguage preference, offer a language
  picker in General settings, and have all three processes write
  AppleLanguages at launch
- Raise the deployment target to macOS 15.6, pin ARCHS to arm64 in
  both xcconfigs and add EXCLUDED_ARCHS = x86_64, dropping the
  target-level ARCHS overrides
- Bump the version to 0.40.0 (505)

A target-level ARCHS overrides the project xcconfig, and Xcode writes
one back whenever a build setting is touched, so EXCLUDED_ARCHS is
there as a backstop and archives no longer carry an x86_64 slice.

Claude-Session: https://claude.ai/code/session_01KFzJ8F9F96FQDwBfRej3ad
@JayDaniel JayDaniel closed this Sep 9, 2026
@JayDaniel
JayDaniel deleted the main-dev branch September 9, 2026 09:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant