Repository navigation
Releases: gregyuzik/AskKit
Release list
AskKit 0.3.1
The composer's hint no longer makes an empty field two lines tall.
Changed
- The composer's hint takes one line, ending in an ellipsis when it is longer than the field is wide,
so an empty field is always one line tall; only a draft grows the capsule. A hint that wrapped held
the capsule two lines tall on an iPhone before the reader had typed a word. VoiceOver still reads the
hint whole. Keep a host's hint short enough to show whole.
AskKit 0.3.0
The public API's shape, and fixes across the engine, the stored chats and the screen, many of them for
VoiceOver and the accessibility sizes, and for a reader on gaze, a pointer or a hardware keyboard.
This version breaks the public API, on purpose. Each value has one type (question kinds, lengths of
time, payloads), a value can no longer say two things at once, names say what they are, the enums that
grow are open, and what only AskKit can drive safely, the stored chats' reads and writes and the
generation gate, is no longer a host's to call. "Updating from 0.2" is the list a host works through.
Three more changes a host sees: AskDomain.perform(_:) runs off the main actor, the confirmation
card's Confirm and the nothing-to-ask button are black on the system blue and red, and AskCommands
puts the newest turn's Copy, Copy answer, Edit & Resend and Ask again in the Mac's File menu.
Updating from 0.2
Work down the list, building as you go: the compiler finds all but the last step for you.
- Names.
AskAskingisAskAnswering, and itsaskrequirements takemode:where they took
origin:.engine.ask(q, origin: …)isengine.ask(q, mode: …):.conversation, the default,
where you passed.typed,.starter,.followUpor.prefilled, and.rerollor.standalone
as before.AskTurn.Failure.retryableisisRetryable.AskTurnStats'msToFirstTool,
msToFirstWord,msToLastWordandmsTotalaretimeToFirstTool,timeToFirstWord,
timeToLastWordandduration, andAskOutcome.millisecondsisduration. @unknown default. End everyswitchof yours over an open enum,AskError,AskTierand
AskTurnEventamong them (README.md's "Switching over AskKit's enums" lists them), with
@unknown default.- Typed kinds and lengths of time.
questionKindsis[AskQuestionKind]andrerollableKinds
Set<AskQuestionKind>, and a kind is a static member:AskScenario(expectedKinds: [.count]),
AskAnswer(questionKind: .pick). A length of time is aDuration:AskDeadline.run(.seconds(5))
where you wroterun(seconds: 5). - Typed payloads, and
try. Drop yourJSONEncoderand hand AskKit the value:
try collector.record(payload: value),try AskProposal(…, payload: value),
try AskAnswer(…, payload: value)andtry answer.replacing(payload: value).
mergePayload(as:_:)takestrytoo, anddeterministicAnswer(for:)isasync throws, so write
trythere, nottry?. - The first screen. Delete
firstScreenPolicyandimplementsDeterministicAnswer, and set
answersWithoutModelto true where either said your domain answers without a model. Keep
nothingToAskCopyonly if an empty overview should show the nothing-to-ask screen: its presence is
the policy. Move the button's label out ofAskBrand(nothingToAskActionLabel:)and its closure out
ofAskScreen(onNothingAction:), into one
AskScreen(store: store, nothingToAskAction: AskNothingToAskAction("Add Your First Item") { … }). - The store, the evaluation and the seam. Build the store with
AskConversationStore(engine:)
and the evaluation withAskEval(domain:seam:log:). Delete everygate:argument and any
AskGenerationGateyou built: your own on-device generations go throughAskGenerationGate.run.
CallAskThreadStore,AskThreadArchiveandAskFeedbackAttachmentsfor nothing but their
initializers and caps, and list the chats with the store'shistory. - States that can't contradict themselves. Build
AskTierStatewithunavailability:for a down
tier, and noisAvailable:. Switch overAskRetryAfter(.rateLimited(until:),
.quotaReached(resetDate:)), readoutcome.failure?.isUngrounded, unwrap
AskPrivateCloudState.of(_:). AshowsDiagnostics: AskDiagnostics.isEnabledcan go: nil, the
card's default now, follows the switch as the card draws. - What else went. Read
AskDiagnostics.isTestFlighton the main actor, and import AskUI where
you nameAskOrigin. DropAskBrand(shortLabel:),AskStarter(bank:)and
AskFeedbackAttachments.removeAll(), and storeAskPersistenceandAskGroundingPolicyby
rawValue, since neither isCodable. - What runs differently, with no compile error:
store.ask(q, origin: .reroll)runs as Another
pick, an empty overview with nonothingToAskCopyshows the ready screen, and a
deterministicAnswer(for:)that throws fails the turn.
Added
AskError.requestNotSupported, for a request the model answering can't take: a capability it
lacks (tool calling, guided generation, reasoning), a generation guide it doesn't support, or prompt
content it can't process. These wereLanguageModelError's threeunsupported…cases, which fell
to the retryable.model(tag:). It offers no Try Again and no coaching, is never handed down to the
other tier, and reads "Couldn't answer that. The model doesn't support what this app asks of it." in
all 25 languages. Where the photo is what the model can't take (unsupportedCapabilityfor
vision, or unsupported content that carries the picture), the turn fails with
.photosNotSupported.AskConversationStore(engine:), which reads the engine's domain (AskEngine.domain, now
public), so the store can't draw, keep and perform by a second copy of the domain that differs from
the one the engine answers by.AskConversationStore(domain:engine:)is for anAskAnswering
engine of a host's own, which carries no domain.AskPrompt.fenceTagsandAskEngine.restartFloorare public, read-only: the tag names a tool's
output must not use, and the 10 s of its deadline a turn that has drawn prose needs left to be asked
again from the start (house rule 5).AppleAskSessionbuilds on watchOS, so a host's own router can wrap its sessions in it there.
AppleModelSeamis still compiled out on watchOS.
Changed
- The public enums that grow are open (
@nonexhaustive):AskError,AskUnavailability,
AskTier,AskActivity,AskTurnEvent,AskReasoningLevel,AskGrounding,AskDiscard.Reason,
AskRetryAfter,AskProposalKind,AskProposalOutcome,AskOutcome.Miss,AskOrigin,
AskStage.Phase,AskProposalPhaseandAskPrivateCloudState. A host'sswitchover one needs
@unknown default, or it doesn't compile, and a case a later version adds lands there instead of
breaking the build. The closed ones,AskTurnMode,AskOutcome.Failure,AskGroundingPolicy,
AskReplyShape,AskEvidencePresentation,AskPersistence,AskPrivateCloudCompute,
AskFeedback.Sentiment,AskChipRow.KindandAskArrival.Kind, are still switched over case by
case. - Question kinds are a type,
AskQuestionKind, so a domain can't misspell one. A kind is a static
member (.count,.pick, …), not aString: a typo such as"cuont"made the model's correct
count read asother, and show tiles.AskDomain.questionKindsis[AskQuestionKind]and
rerollableKindsSet<AskQuestionKind>, andAskAnswer,AskFeedback,AskScenario,
AskOutcomeandAskOutcome.Misscarry kinds too.AskQuestionKind(rawValue:)never fails, and a
kind is stored as its bare word, so saved chats read back unchanged. The model is offered the same
eleven words, and the[feedback]line and the evaluation's table print them as before. - The engine runs a turn in a mode, and the store keeps where its question came from.
AskEngine.asktakesmode: AskTurnMode(.conversation, the default;.reroll, with Another
pick's steer at temperature 0.8;.standalone, a one-shot that neither reads nor replaces the kept
conversation) in place oforigin:.AskOriginmoves from AskCore to AskUI with the same name, and
says only where a question came from:.typed,.starter,.followUp,.prefilledor.reroll,
with no.standalone, raw value orCodable.AskTurn.origin,AskTurn's initializer and
AskConversationStore.ask(_:origin:)take AskUI's, and a target that imports AskCore alone no
longer has the type. - The store asks every origin as itself.
store.ask(q, origin: .reroll)runs as Another pick,
hotter and steered away from the last answer's picks, where it was asked as a typed question, and
leaves the composer's draft, photo and scope where they are. AskAskingisAskAnswering, since the conformer answers and the store asks, and itsask
requirements takemode: AskTurnMode.AskTurn.Failure.retryableisisRetryable, its
initializer's label included, asAskError.isRetryablealready says it; a thread file's
failureIsRetryablekey is unchanged.- An evaluation builds its own engine:
AskEval(domain:seam:log:), in place of
AskEval(engine:). Handed the thread's engine, the evaluation reset it before its first scenario,
dropping the reader's conversation and warm session and leaving the store'shasKeptConversation
stale. It can share the thread's domain and seam. - A payload is your own value, which AskKit encodes as JSON.
AskCollector.record(payload:),
AskProposal(…, payload:),AskAnswer(…, payload:)andAskAnswer.replacing(payload:)take your
value (some Encodable & Sendable, orsome Encodablefor a proposal), where they tookData, and
throw the encoder'sEncodingErrorfor a value that doesn't encode, such as a NaN, and for a
Data, which is a payload already encoded. An oversize payload still makesrecord(payload:)
returnfalse, which a tool can tell the model.mergePayload(as:_:)throws for a result that
doesn't encode, where it returnedfalse. A proposal or an answer with no payload takes the
initializer without one, which doesn't throw. Payloads in chat files 0.2.0 wrote still read back. AskDomain.deterministicAnswer(for:)isasync throws. A throw fails the turn and asks no
model: anAskError...
AskKit 0.2.0
A privacy manifest for AskCore, and a plainer composer that no longer needs Xcode's Metal Toolchain to
build. The public API is unchanged.
Added
- A privacy manifest for AskCore.
AskCore's resource bundle carries aPrivacyInfo.xcprivacy, so
an app that embeds AskKit gets a complete privacy report. It declares the two required-reason APIs
AskCore calls: user defaults forCA92.1, read only for the launch's own arguments (ASK_LIVE_PCC),
and the file timestamp forC617.1, the modification date of a chat file in the app's own container.
It declares no tracking and no collected data: AskKit sends nothing to its developer or any third
party. AskUI calls none of these APIs, and carries no manifest.
Removed
- The Glass Rod round the composer. The spectrum look's clear glass tube of colors round the
composer's rim, there at rest and drifting while a turn was in flight, was too distracting, and the
composer was better without it: it is plain glass again, in the accent's tint. A spectrum tints the
send circle and the streaming caret, on the slot's face too, and nothing else. With the rod went
AskUI's only Metal shader, so building AskKit no longer needs Xcode's Metal Toolchain.
AskKit 0.1.0
The first public release: a complete Ask screen over Apple's Foundation Models, for any kind of app. A
reader asks in their own words and gets a short answer, from the app's data, through the tools the app
gives the model, or from the model alone where the app allows it. The app implements one protocol,
AskDomain, and AskKit supplies the engine (AskCore) and the SwiftUI screen (AskUI).
It needs Xcode 27.2 or later on a Mac with Apple silicon, Swift 6.4, and Xcode's Metal Toolchain
component (xcodebuild -downloadComponent MetalToolchain), since AskUI compiles a Metal shader. Every
platform's deployment target is 27.2. The on-device model needs Apple Intelligence on the reader's
device, and Private Cloud Compute, which is optional, needs Apple's managed entitlement.
Added
AskCore, the engine, with no UI. A domain gives it a brand, tools and instructions; one over the
app's own rows adds its row type, its search's arguments, an overview and a search, and one whose
tools read no rows leaves those to their defaults.- Private Cloud Compute answers when it can serve and the on-device model when it can't, behind one
model seam (AskModelSeam, whichAppleModelSeamimplements). When Private Cloud Compute goes out
mid-turn, the turn is handed down once to the on-device model. The seam sends Private Cloud Compute
nothing unless the host declares the entitlement, and keeps a development build off it unless the
run opts in (ASK_LIVE_PCC=1), so the developer's own daily quota isn't spent. - Grounding is the domain's policy (
AskGroundingPolicy)..retrieval, the default, keeps only
answers grounded in the app's data, and when a first answer read nothing it runs the app's own
search and asks again (the grounded retry)..toolCallkeeps answers a tool call grounded, and
.notRequiredkeeps every answer. Each answer records what grounded it (AskAnswer.grounding). - Actions the model proposes (
AskProposal), which the reader confirms on a card before the domain's
perform(_:)carries them out, or which open the app's own editor. - The line under an answer is the domain's (
AskDomain.evidence(for:)): AskKit's count of the rows
it read, the domain's own words, or no line. An answer nothing grounded says it isn't from the
app's data. - A reply with no tiles, for a domain whose answers never show any (
AskReplyShape.prose): on the
device its schema costs 192 tokens of the window where the full reply's costs 704. - A conversation that carries from turn to turn, one deadline over the whole turn (the domain's
hooks, every generation, the grounded retry and the hand-down), and every failure triaged into an
AskErrorwith a line for the reader. - Tool calls through
AskToolContext.withToolCall(_:_:), fields throughAskPrompt.clamp, and what
AskKit composes fenced, with its fence tags redacted in any spelling that compatibility
decomposition (NFKD) maps to the tag once marks are dropped. AskLink, a deep link that prefills the composer and never sends;AskEngine.answerOnce(_:), one
answer outside the thread, for a Siri intent; andAskEval, which runs scenarios through a real
engine.
- Private Cloud Compute answers when it can serve and the on-device model when it can't, behind one
AskUI, the Ask screen for iOS, macOS and visionOS, and theAskConversationStorethat drives
it, which keeps the thread and the earlier chats on disk, where no failed write or interrupted change
drops a chat.- The first screen with the app's starters; the thread, with the wait row, the prose as it streams,
the app's tiles, the evidence line, the confirmation card and follow-up chips; chat history; the
composer in the tab bar slot on iOS; menu commands on the Mac (AskCommands); and
AskPrivateCloudCardfor the app's settings. - A host sets its own views with modifiers on
AskScreen(store:), and opens a tile's row or a
hand-off's editor through a class of its own conformed toAskItemRouterorAskDraftRouter. - The spectrum look, opt-in:
AskScreen(store:spectrum:)takes anAskSpectrum, such as
.aurora. Its colors light the Glass Rod, a clear glass tube round the composer's rim drawn by a
Metal shader, there at rest and drifting while a turn is in flight, and the send circle and the
streaming caret take a gradient of them. Increase Contrast puts the accent back and drops the rod,
and Reduce Motion holds its colors still. - On iPhone Duo's inner display the screen arranges itself around the fold.
- The first screen with the app's starters; the thread, with the wait row, the prose as it streams,
- Strings in 25 languages, in two String Catalogs, with a check that fails a missing locale, plural
or placeholder. - Five platforms. Both libraries compile for iOS, macOS, visionOS, tvOS and watchOS, so a host's
shared package can link them on every platform it ships. The screen shows on iOS, macOS and visionOS. - A demo app for iOS, macOS and visionOS: a library of thirty items behind one search tool, or,
with-askAssistant, an assistant with no rows of its own that converts units and proposes
reminders. A launch argument sets up each state of the screen, and-askScriptedAnsweranswers
without a model.