Repository navigation
Agent harness adoption, Part A: TypeScript setup, Model/Tool wrappers, event mapping - #41
Merged
Merged
Conversation
Prompt caching rewrites string content into text blocks carrying cache_control. The Messages adapter then joined system contents as strings, sending `system: "[object Object]"` on every Anthropic request with a system prompt, and rebuilt text and image parts without their cache_control, so no breakpoint ever reached Anthropic. System content with blocks now becomes Anthropic system blocks, and text and image parts keep their cache_control. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JqbJB9ca1EWFbCcpWy52oY
Node 24 runs .ts files directly by stripping types, so there is no build step: Docker and Electron keep running source files. Checked first that the packaged desktop app can do this: Electron 43.8.7 (Node 24.22.1) imports a .ts entry point, and .ts -> .ts and .ts -> .js imports, both unpacked and from inside app.asar. - tsconfig.json: allowJs, checkJs off, noEmit, strict, erasableSyntaxOnly, allowImportingTsExtensions, verbatimModuleSyntax, nodenext. - `npm run typecheck`, run by ci.sh. - Jest strips .ts with @swc/jest; JavaScript stays untransformed ESM. - ESLint parses .ts with typescript-eslint. - Electron packaging leaves out .ts tests and tsconfig.json. Existing JavaScript is unchanged. Refs #40 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JqbJB9ca1EWFbCcpWy52oY
Part A of #40: ChatForge's providers and tools now implement the agent harness contracts, and harness turn events have a mapping onto today's SSE stream and persistence. Nothing runs on the harness yet; Part B needs the published package. - contracts.ts mirrors the package's Model, Tool, Item and TurnEvent types until the package is installed. - chatMessages.ts converts Item history to and from Chat Completions messages without loss; messages Item can't represent travel as provider_input. - One Model wrapper per provider (OpenAI, Anthropic, Gemini, llama.cpp) on a shared streaming core that maps provider chunks to ModelEvents. Prompt-cache breakpoints are placed inside the wrappers. Continuation is sent only to Responses-API providers, and a rejected one is retried self-contained. - toHarnessTool maps createTool directly (parameters, validate, handler, no approval); the user id reaches tools as ctx.app.userId. - TurnEventSink lands each TurnEvent in the SSE stream and persistence as handleToolsStreaming does today. Withdrawn deltas are taken back out of the checkpoint with the new SimplifiedPersistence.truncateContent. - createOpenAIRequest accepts a provider instance instead of resolving one from the database per request. Each wrapper is checked by streaming a recorded upstream response through the real provider and adapter. docs/agent-harness-event-mapping.md has the mapping, the deliberate differences from the legacy loop, and the gaps to report back to the harness. Refs #40 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JqbJB9ca1EWFbCcpWy52oY
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part A of #40. ChatForge's providers and tools now implement the agent harness contracts, and harness turn events have a tested mapping onto today's SSE stream and persistence. Nothing runs on the harness yet: Part B needs the published package. Today's request path is unchanged apart from the Anthropic fix below.
Commits
1.
fix(anthropic): prompt-cache blocks were breaking the system prompt. Can be cherry-picked on its own.On
main, prompt caching rewrites the system message into content blocks, and the Messages adapter then joined them as strings. The result wassystem: "[object Object]"on every Anthropic request with a system prompt. The adapter also rebuilt text and image parts without theircache_control, so no cache breakpoint ever reached Anthropic. System content with blocks now becomes Anthropic system blocks, and parts keepcache_control. Regression test inmessages_adapter.test.js.2.
build(backend): TypeScript for new code..ts. Electron 43.8.7 (Node 24.22.1) imports a.tsentry point, including.ts→.tsand.ts→.jsimports, both unpacked and from insideapp.asar. This used a minimal test app, not a full ChatForge package build.backend/tsconfig.jsonhasallowJs,checkJs: false,noEmit,strict,erasableSyntaxOnly,allowImportingTsExtensions,verbatimModuleSyntaxandnodenext.typecheckscript, run byci.sh. Jest strips.tswith@swc/jest, and ESLint parses.tswithtypescript-eslint..tstests andtsconfig.json. Existing JavaScript is untouched.3.
feat(harness): wrappers and event mapping, inbackend/src/lib/harness/:contracts.ts: a temporary local copy of the package'sModel,Tool,ItemandTurnEventtypes. Delete it when the package lands.chatMessages.ts: converts betweenItem[]and Chat Completions messages without loss. Messages thatItemcan't represent travel asprovider_input.providerModel.tsandmodels.ts: oneModelper provider (OpenAI, Anthropic, Gemini, llama.cpp) on a shared streaming core.previous_response_idis sent only to Responses-API providers. If the provider rejects it, the request is retried with the full history.retryingevents.tools.ts: mapscreateTooldirectly ontoTool(parameters,validate,handler, no approval). The user id reaches tools asctx.app.userId.turnEventSink.ts: lands eachTurnEventin SSE and persistence the wayhandleToolsStreamingdoes now. Withdrawn deltas are removed from checkpoints through the newSimplifiedPersistence.truncateContent.createOpenAIRequestnow accepts a provider instance instead of resolving one from the DB on every request.docs/agent-harness-event-mapping.md: the full mapping, the deliberate differences from the legacy loop, the gaps to report back to the harness, and a Part B checklist.Behavior changes once Part B switches over
previous_response_idis no longer sent to plain Chat Completions providers. The legacy loop sends achatcmpl-…id from the second iteration on.error. The legacy path saves it assuccess.content_withdrawnSSE event. The frontend doesn't handle it yet; that's on the Part B checklist.For the harness (Term2)
ModelEventhas no channel for provider extras while the response is streaming.Usagehas no field for reasoning tokens.TurnSpec.limitsrequires both caps, but ChatForge only has an iteration cap.Testing
npm run typecheckandnpm run lintare clean.fetchmocked.Generated by Claude Code