kimaki 0.4.82 → 0.4.84
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.
- package/LICENSE +21 -0
- package/dist/anthropic-auth-plugin.js +7 -0
- package/dist/cli.js +51 -7
- package/dist/commands/abort.js +5 -16
- package/dist/commands/action-buttons.js +3 -3
- package/dist/commands/add-project.js +1 -1
- package/dist/commands/ask-question.js +3 -3
- package/dist/commands/context-usage.js +1 -1
- package/dist/commands/create-new-project.js +1 -1
- package/dist/commands/fork.js +11 -8
- package/dist/commands/merge-worktree.js +1 -1
- package/dist/commands/new-worktree.js +63 -44
- package/dist/commands/remove-project.js +1 -1
- package/dist/commands/resume.js +11 -8
- package/dist/commands/screenshare.js +14 -6
- package/dist/commands/screenshare.test.js +20 -0
- package/dist/commands/session.js +1 -1
- package/dist/commands/undo-redo.js +91 -7
- package/dist/commands/user-command.js +1 -1
- package/dist/config.js +16 -1
- package/dist/database.js +53 -2
- package/dist/db.js +6 -0
- package/dist/discord-bot.js +48 -85
- package/dist/discord-command-registration.js +1 -1
- package/dist/external-opencode-sync.js +515 -0
- package/dist/external-opencode-sync.test.js +151 -0
- package/dist/gateway-proxy.e2e.test.js +8 -5
- package/dist/genai.js +1 -1
- package/dist/generated/enums.js +4 -0
- package/dist/generated/internal/class.js +4 -4
- package/dist/generated/internal/prismaNamespace.js +1 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +1 -0
- package/dist/generated/models/external_session_pending_prompts.js +1 -0
- package/dist/hrana-server.js +14 -285
- package/dist/hrana-server.test.js +4 -2
- package/dist/kimaki-opencode-plugin-loading.e2e.test.js +7 -0
- package/dist/kimaki-opencode-plugin.js +2 -0
- package/dist/kitty-graphics-parser.js +3 -0
- package/dist/kitty-graphics-parser.test.js +276 -0
- package/dist/kitty-graphics-plugin.js +3 -0
- package/dist/markdown.js +4 -4
- package/dist/markdown.test.js +1 -1
- package/dist/message-formatting.js +54 -15
- package/dist/onboarding-tutorial.js +1 -1
- package/dist/openai-realtime.js +9 -13
- package/dist/opencode.js +28 -5
- package/dist/queue-advanced-e2e-setup.js +89 -0
- package/dist/queue-advanced-permissions-typing.e2e.test.js +5 -5
- package/dist/queue-advanced-typing.e2e.test.js +9 -22
- package/dist/queue-question-select-drain.e2e.test.js +117 -0
- package/dist/session-handler/event-stream-state.js +101 -7
- package/dist/session-handler/event-stream-state.test.js +7 -3
- package/dist/session-handler/thread-session-runtime.js +120 -9
- package/dist/store.js +1 -0
- package/dist/system-message.js +22 -4
- package/dist/system-message.test.js +19 -0
- package/dist/task-runner.js +1 -1
- package/dist/thread-message-queue.e2e.test.js +8 -14
- package/dist/tools.js +1 -1
- package/dist/undo-redo.e2e.test.js +20 -25
- package/package.json +10 -6
- package/schema.prisma +6 -0
- package/skills/errore/SKILL.md +40 -13
- package/skills/goke/SKILL.md +12 -0
- package/skills/lintcn/SKILL.md +868 -0
- package/skills/npm-package/SKILL.md +1 -0
- package/skills/proxyman/SKILL.md +215 -0
- package/skills/spiceflow/SKILL.md +1 -1
- package/skills/usecomputer/SKILL.md +339 -0
- package/src/ai-tool-to-genai.ts +1 -0
- package/src/anthropic-auth-plugin.ts +7 -0
- package/src/cli.ts +59 -6
- package/src/commands/abort.ts +6 -16
- package/src/commands/action-buttons.ts +5 -1
- package/src/commands/add-project.ts +1 -1
- package/src/commands/ask-question.ts +5 -2
- package/src/commands/context-usage.ts +1 -1
- package/src/commands/create-new-project.ts +1 -1
- package/src/commands/fork.ts +12 -11
- package/src/commands/merge-worktree.ts +1 -1
- package/src/commands/new-worktree.ts +74 -55
- package/src/commands/remove-project.ts +1 -1
- package/src/commands/resume.ts +12 -10
- package/src/commands/screenshare.test.ts +30 -0
- package/src/commands/screenshare.ts +18 -6
- package/src/commands/session.ts +1 -1
- package/src/commands/undo-redo.ts +108 -10
- package/src/commands/user-command.ts +1 -1
- package/src/config.ts +19 -1
- package/src/database.ts +72 -3
- package/src/db.ts +8 -0
- package/src/discord-bot.ts +58 -93
- package/src/discord-command-registration.ts +1 -1
- package/src/external-opencode-sync.ts +729 -0
- package/src/gateway-proxy.e2e.test.ts +9 -5
- package/src/genai.ts +3 -3
- package/src/generated/commonInputTypes.ts +34 -0
- package/src/generated/enums.ts +8 -0
- package/src/generated/internal/class.ts +4 -4
- package/src/generated/internal/prismaNamespace.ts +8 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +1 -0
- package/src/generated/models/thread_sessions.ts +53 -1
- package/src/hrana-server.test.ts +8 -2
- package/src/hrana-server.ts +18 -390
- package/src/kimaki-opencode-plugin-loading.e2e.test.ts +7 -0
- package/src/kimaki-opencode-plugin.ts +2 -0
- package/src/markdown.test.ts +1 -1
- package/src/markdown.ts +4 -4
- package/src/message-formatting.ts +66 -17
- package/src/onboarding-tutorial.ts +1 -1
- package/src/openai-realtime.ts +6 -10
- package/src/opencode.ts +31 -7
- package/src/queue-advanced-e2e-setup.ts +92 -0
- package/src/queue-advanced-permissions-typing.e2e.test.ts +5 -5
- package/src/queue-advanced-typing.e2e.test.ts +9 -22
- package/src/queue-question-select-drain.e2e.test.ts +149 -0
- package/src/schema.sql +1 -0
- package/src/session-handler/event-stream-state.test.ts +7 -2
- package/src/session-handler/event-stream-state.ts +128 -7
- package/src/session-handler/thread-runtime-state.ts +5 -0
- package/src/session-handler/thread-session-runtime.ts +153 -11
- package/src/store.ts +8 -0
- package/src/system-message.ts +27 -4
- package/src/task-runner.ts +1 -1
- package/src/thread-message-queue.e2e.test.ts +8 -14
- package/src/tools.ts +1 -1
- package/src/undo-redo.e2e.test.ts +28 -26
- package/skills/jitter/node_modules/.bin/esbuild +0 -21
- package/skills/jitter/node_modules/.bin/tsc +0 -21
- package/skills/jitter/node_modules/.bin/tsserver +0 -21
- package/skills/jitter/node_modules/typescript/LICENSE.txt +0 -55
- package/skills/jitter/node_modules/typescript/README.md +0 -50
- package/skills/jitter/node_modules/typescript/SECURITY.md +0 -41
- package/skills/jitter/node_modules/typescript/ThirdPartyNoticeText.txt +0 -193
- package/skills/jitter/node_modules/typescript/bin/tsc +0 -2
- package/skills/jitter/node_modules/typescript/bin/tsserver +0 -2
- package/skills/jitter/node_modules/typescript/lib/_tsc.js +0 -133792
- package/skills/jitter/node_modules/typescript/lib/_tsserver.js +0 -659
- package/skills/jitter/node_modules/typescript/lib/_typingsInstaller.js +0 -222
- package/skills/jitter/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/de/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/es/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/it/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/lib.d.ts +0 -22
- package/skills/jitter/node_modules/typescript/lib/lib.decorators.d.ts +0 -384
- package/skills/jitter/node_modules/typescript/lib/lib.decorators.legacy.d.ts +0 -22
- package/skills/jitter/node_modules/typescript/lib/lib.dom.asynciterable.d.ts +0 -41
- package/skills/jitter/node_modules/typescript/lib/lib.dom.d.ts +0 -39429
- package/skills/jitter/node_modules/typescript/lib/lib.dom.iterable.d.ts +0 -571
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.collection.d.ts +0 -147
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.core.d.ts +0 -597
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.d.ts +0 -28
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.generator.d.ts +0 -77
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.iterable.d.ts +0 -605
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.promise.d.ts +0 -81
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.proxy.d.ts +0 -128
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.reflect.d.ts +0 -144
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.symbol.d.ts +0 -46
- package/skills/jitter/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts +0 -326
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.array.include.d.ts +0 -116
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.full.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2016.intl.d.ts +0 -31
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.d.ts +0 -26
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.date.d.ts +0 -31
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.full.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.intl.d.ts +0 -44
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.object.d.ts +0 -49
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts +0 -135
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.string.d.ts +0 -45
- package/skills/jitter/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts +0 -53
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts +0 -77
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts +0 -53
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.intl.d.ts +0 -83
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.promise.d.ts +0 -30
- package/skills/jitter/node_modules/typescript/lib/lib.es2018.regexp.d.ts +0 -37
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.array.d.ts +0 -79
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.intl.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.object.d.ts +0 -33
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.string.d.ts +0 -37
- package/skills/jitter/node_modules/typescript/lib/lib.es2019.symbol.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.bigint.d.ts +0 -765
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.d.ts +0 -27
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.date.d.ts +0 -42
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.intl.d.ts +0 -474
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.number.d.ts +0 -28
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.promise.d.ts +0 -47
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts +0 -99
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.string.d.ts +0 -44
- package/skills/jitter/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts +0 -41
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.intl.d.ts +0 -166
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.promise.d.ts +0 -48
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.string.d.ts +0 -33
- package/skills/jitter/node_modules/typescript/lib/lib.es2021.weakref.d.ts +0 -78
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.array.d.ts +0 -121
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.error.d.ts +0 -75
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.intl.d.ts +0 -145
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.object.d.ts +0 -26
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.regexp.d.ts +0 -39
- package/skills/jitter/node_modules/typescript/lib/lib.es2022.string.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.array.d.ts +0 -924
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.collection.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.d.ts +0 -22
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2023.intl.d.ts +0 -56
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts +0 -65
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.collection.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.d.ts +0 -26
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.object.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.promise.d.ts +0 -35
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.regexp.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts +0 -68
- package/skills/jitter/node_modules/typescript/lib/lib.es2024.string.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.es5.d.ts +0 -4601
- package/skills/jitter/node_modules/typescript/lib/lib.es6.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.array.d.ts +0 -35
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.collection.d.ts +0 -96
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.d.ts +0 -29
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.decorators.d.ts +0 -28
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.disposable.d.ts +0 -193
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.error.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.float16.d.ts +0 -443
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.full.d.ts +0 -24
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.intl.d.ts +0 -21
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.iterator.d.ts +0 -148
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.promise.d.ts +0 -34
- package/skills/jitter/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts +0 -25
- package/skills/jitter/node_modules/typescript/lib/lib.scripthost.d.ts +0 -322
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts +0 -41
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.d.ts +0 -13150
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.importscripts.d.ts +0 -23
- package/skills/jitter/node_modules/typescript/lib/lib.webworker.iterable.d.ts +0 -340
- package/skills/jitter/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/tsc.js +0 -8
- package/skills/jitter/node_modules/typescript/lib/tsserver.js +0 -8
- package/skills/jitter/node_modules/typescript/lib/tsserverlibrary.d.ts +0 -17
- package/skills/jitter/node_modules/typescript/lib/tsserverlibrary.js +0 -21
- package/skills/jitter/node_modules/typescript/lib/typesMap.json +0 -497
- package/skills/jitter/node_modules/typescript/lib/typescript.d.ts +0 -11438
- package/skills/jitter/node_modules/typescript/lib/typescript.js +0 -200253
- package/skills/jitter/node_modules/typescript/lib/typingsInstaller.js +0 -8
- package/skills/jitter/node_modules/typescript/lib/watchGuard.js +0 -53
- package/skills/jitter/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +0 -2122
- package/skills/jitter/node_modules/typescript/node_modules/.bin/tsc +0 -21
- package/skills/jitter/node_modules/typescript/node_modules/.bin/tsserver +0 -21
- package/skills/jitter/node_modules/typescript/package.json +0 -120
package/src/hrana-server.ts
CHANGED
|
@@ -2,32 +2,12 @@
|
|
|
2
2
|
// Backed by the `libsql` npm package (better-sqlite3 API).
|
|
3
3
|
// Binds to the fixed lock port for single-instance enforcement.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// Protocol logic is implemented in the `libsqlproxy` package.
|
|
6
|
+
// This file handles: server lifecycle, single-instance enforcement,
|
|
7
|
+
// auth, and kimaki-specific endpoints (/kimaki/wake, /health).
|
|
7
8
|
//
|
|
8
9
|
// Hrana v2 protocol spec ("Hrana over HTTP"):
|
|
9
10
|
// https://github.com/tursodatabase/libsql/blob/main/docs/HTTP_V2_SPEC.md
|
|
10
|
-
//
|
|
11
|
-
// The protocol exposes stateful streams over HTTP. Each stream corresponds
|
|
12
|
-
// to a SQLite connection. Requests on the same stream are tied together
|
|
13
|
-
// via a "baton" — the server returns a baton in every response, and the
|
|
14
|
-
// client includes it in the next request. Stream-scoped state includes
|
|
15
|
-
// SQL text cached via store_sql (referenced by sql_id in later stmts).
|
|
16
|
-
//
|
|
17
|
-
// Request types implemented:
|
|
18
|
-
// execute — run a single SQL statement, return cols/rows/changes
|
|
19
|
-
// batch — run multiple steps with conditional execution (ok/not/and/or)
|
|
20
|
-
// sequence — split raw SQL by semicolons, execute each (no results)
|
|
21
|
-
// store_sql — cache SQL text under a numeric sql_id for the stream
|
|
22
|
-
// close_sql — remove a cached sql_id
|
|
23
|
-
// close — close the stream (baton becomes null)
|
|
24
|
-
//
|
|
25
|
-
// Value encoding (SQLite → Hrana JSON):
|
|
26
|
-
// INTEGER → {"type":"integer","value":"42"} (string, not number)
|
|
27
|
-
// REAL → {"type":"float","value":3.14}
|
|
28
|
-
// TEXT → {"type":"text","value":"hello"}
|
|
29
|
-
// BLOB → {"type":"blob","base64":"..."}
|
|
30
|
-
// NULL → {"type":"null"}
|
|
31
11
|
|
|
32
12
|
import fs from 'node:fs'
|
|
33
13
|
import http from 'node:http'
|
|
@@ -35,6 +15,11 @@ import path from 'node:path'
|
|
|
35
15
|
import crypto from 'node:crypto'
|
|
36
16
|
import Database from 'libsql'
|
|
37
17
|
import * as errore from 'errore'
|
|
18
|
+
import {
|
|
19
|
+
createLibsqlHandler,
|
|
20
|
+
createLibsqlNodeHandler,
|
|
21
|
+
libsqlExecutor,
|
|
22
|
+
} from 'libsqlproxy'
|
|
38
23
|
import { createLogger, LogPrefix } from './logger.js'
|
|
39
24
|
import { ServerStartError, FetchError } from './errors.js'
|
|
40
25
|
import { getLockPort } from './config.js'
|
|
@@ -156,9 +141,11 @@ export async function startHranaServer({
|
|
|
156
141
|
database.exec('PRAGMA busy_timeout = 5000')
|
|
157
142
|
db = database
|
|
158
143
|
|
|
159
|
-
|
|
144
|
+
// Create the Hrana handler using libsqlproxy
|
|
145
|
+
const hranaFetchHandler = createLibsqlHandler(libsqlExecutor(database))
|
|
146
|
+
const hranaNodeHandler = createLibsqlNodeHandler(hranaFetchHandler)
|
|
160
147
|
|
|
161
|
-
// Combined handler:
|
|
148
|
+
// Combined handler: kimaki-specific endpoints + hrana protocol
|
|
162
149
|
const handler: http.RequestListener = async (req, res) => {
|
|
163
150
|
const pathname = new URL(req.url || '/', 'http://localhost').pathname
|
|
164
151
|
if (pathname === '/kimaki/wake') {
|
|
@@ -182,18 +169,20 @@ export async function startHranaServer({
|
|
|
182
169
|
res.end(JSON.stringify({ ready: true }))
|
|
183
170
|
return
|
|
184
171
|
}
|
|
185
|
-
//
|
|
172
|
+
// Health check — no auth required
|
|
186
173
|
if (pathname === '/health') {
|
|
187
|
-
|
|
174
|
+
res.writeHead(200, { 'content-type': 'application/json' })
|
|
175
|
+
res.end(JSON.stringify({ status: 'ok', pid: process.pid }))
|
|
188
176
|
return
|
|
189
177
|
}
|
|
178
|
+
// Hrana routes: /v2, /v2/pipeline — require auth
|
|
190
179
|
if (pathname === '/v2' || pathname === '/v2/pipeline') {
|
|
191
180
|
if (!isAuthorizedRequest(req)) {
|
|
192
181
|
res.writeHead(401, { 'content-type': 'application/json' })
|
|
193
182
|
res.end(JSON.stringify({ error: 'unauthorized' }))
|
|
194
183
|
return
|
|
195
184
|
}
|
|
196
|
-
|
|
185
|
+
hranaNodeHandler(req, res)
|
|
197
186
|
return
|
|
198
187
|
}
|
|
199
188
|
res.writeHead(404)
|
|
@@ -253,368 +242,7 @@ export async function stopHranaServer() {
|
|
|
253
242
|
hranaLogger.log('Hrana server stopped')
|
|
254
243
|
}
|
|
255
244
|
|
|
256
|
-
// ──
|
|
257
|
-
|
|
258
|
-
type HranaValue =
|
|
259
|
-
| { type: 'null' }
|
|
260
|
-
| { type: 'integer'; value: string }
|
|
261
|
-
| { type: 'float'; value: number }
|
|
262
|
-
| { type: 'text'; value: string }
|
|
263
|
-
| { type: 'blob'; base64: string }
|
|
264
|
-
|
|
265
|
-
interface HranaStmt {
|
|
266
|
-
sql?: string
|
|
267
|
-
sql_id?: number
|
|
268
|
-
args?: HranaValue[]
|
|
269
|
-
named_args?: Array<{ name: string; value: HranaValue }>
|
|
270
|
-
want_rows?: boolean
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
interface HranaCondition {
|
|
274
|
-
type: 'ok' | 'not' | 'and' | 'or'
|
|
275
|
-
step?: number
|
|
276
|
-
cond?: HranaCondition
|
|
277
|
-
conds?: HranaCondition[]
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
interface HranaBatchStep {
|
|
281
|
-
stmt: HranaStmt
|
|
282
|
-
condition?: HranaCondition | null
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
interface HranaRequest {
|
|
286
|
-
type: string
|
|
287
|
-
stmt?: HranaStmt
|
|
288
|
-
batch?: { steps: HranaBatchStep[] }
|
|
289
|
-
sql?: string
|
|
290
|
-
sql_id?: number
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
interface HranaPipelineRequest {
|
|
294
|
-
baton: string | null
|
|
295
|
-
requests: HranaRequest[]
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
interface HranaColInfo {
|
|
299
|
-
name: string
|
|
300
|
-
decltype: string | null
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
interface HranaExecuteResult {
|
|
304
|
-
cols: HranaColInfo[]
|
|
305
|
-
rows: HranaValue[][]
|
|
306
|
-
affected_row_count: number
|
|
307
|
-
last_insert_rowid: string | null
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// ── Value encoding/decoding ──────────────────────────────────────────────
|
|
311
|
-
|
|
312
|
-
function encodeValue(val: unknown): HranaValue {
|
|
313
|
-
if (val === null || val === undefined) return { type: 'null' }
|
|
314
|
-
if (typeof val === 'bigint') return { type: 'integer', value: val.toString() }
|
|
315
|
-
if (typeof val === 'number') {
|
|
316
|
-
if (Number.isInteger(val)) return { type: 'integer', value: val.toString() }
|
|
317
|
-
return { type: 'float', value: val }
|
|
318
|
-
}
|
|
319
|
-
if (typeof val === 'string') return { type: 'text', value: val }
|
|
320
|
-
if (Buffer.isBuffer(val))
|
|
321
|
-
return { type: 'blob', base64: val.toString('base64') }
|
|
322
|
-
if (val instanceof Uint8Array)
|
|
323
|
-
return { type: 'blob', base64: Buffer.from(val).toString('base64') }
|
|
324
|
-
return { type: 'text', value: String(val) }
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function decodeValue(val: HranaValue): unknown {
|
|
328
|
-
if (val.type === 'null') return null
|
|
329
|
-
if (val.type === 'integer') {
|
|
330
|
-
const n = Number(val.value)
|
|
331
|
-
return Number.isSafeInteger(n) ? n : BigInt(val.value)
|
|
332
|
-
}
|
|
333
|
-
if (val.type === 'float') return val.value
|
|
334
|
-
if (val.type === 'text') return val.value
|
|
335
|
-
if (val.type === 'blob') return Buffer.from(val.base64, 'base64')
|
|
336
|
-
return null
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// ── Statement execution ──────────────────────────────────────────────────
|
|
340
|
-
|
|
341
|
-
// SqliteError from libsql has a `code` property but catch gives Error.
|
|
342
|
-
function getSqliteErrorCode(err: Error): string {
|
|
343
|
-
return (err as unknown as { code?: string }).code ?? 'SQLITE_ERROR'
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function resolveStmtSql(
|
|
347
|
-
stmt: HranaStmt,
|
|
348
|
-
sqlStore: Map<number, string>,
|
|
349
|
-
): string {
|
|
350
|
-
if (stmt.sql != null) return stmt.sql
|
|
351
|
-
if (stmt.sql_id != null) return sqlStore.get(stmt.sql_id) ?? ''
|
|
352
|
-
return ''
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function bindParams(stmt: HranaStmt): unknown[] {
|
|
356
|
-
if (stmt.named_args && stmt.named_args.length > 0) {
|
|
357
|
-
const named: Record<string, unknown> = {}
|
|
358
|
-
for (const na of stmt.named_args) {
|
|
359
|
-
named[na.name] = decodeValue(na.value)
|
|
360
|
-
}
|
|
361
|
-
return [named]
|
|
362
|
-
}
|
|
363
|
-
return (stmt.args ?? []).map(decodeValue)
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function executeStmt(
|
|
367
|
-
database: Database.Database,
|
|
368
|
-
stmt: HranaStmt,
|
|
369
|
-
sqlStore: Map<number, string>,
|
|
370
|
-
): HranaExecuteResult {
|
|
371
|
-
const sql = resolveStmtSql(stmt, sqlStore)
|
|
372
|
-
const prepared = database.prepare(sql)
|
|
373
|
-
const params = bindParams(stmt)
|
|
374
|
-
|
|
375
|
-
if (prepared.reader) {
|
|
376
|
-
const cols = prepared.columns()
|
|
377
|
-
const rows = prepared.all(...params) as Record<string, unknown>[]
|
|
378
|
-
return {
|
|
379
|
-
cols: cols.map((c) => ({ name: c.name, decltype: c.type })),
|
|
380
|
-
rows: rows.map((row) => cols.map((c) => encodeValue(row[c.name]))),
|
|
381
|
-
affected_row_count: 0,
|
|
382
|
-
last_insert_rowid: null,
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const result = prepared.run(...params)
|
|
387
|
-
return {
|
|
388
|
-
cols: [],
|
|
389
|
-
rows: [],
|
|
390
|
-
affected_row_count: result.changes,
|
|
391
|
-
last_insert_rowid:
|
|
392
|
-
result.lastInsertRowid != null ? result.lastInsertRowid.toString() : null,
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// ── Batch condition evaluation ───────────────────────────────────────────
|
|
397
|
-
|
|
398
|
-
function evaluateCondition(
|
|
399
|
-
cond: HranaCondition | null | undefined,
|
|
400
|
-
stepResults: Array<HranaExecuteResult | null>,
|
|
401
|
-
stepErrors: Array<{ message: string; code: string } | null>,
|
|
402
|
-
): boolean {
|
|
403
|
-
if (!cond) return true
|
|
404
|
-
if (cond.type === 'ok')
|
|
405
|
-
return stepErrors[cond.step!] === null && stepResults[cond.step!] !== null
|
|
406
|
-
if (cond.type === 'not')
|
|
407
|
-
return !evaluateCondition(cond.cond, stepResults, stepErrors)
|
|
408
|
-
if (cond.type === 'and')
|
|
409
|
-
return (cond.conds ?? []).every((c) =>
|
|
410
|
-
evaluateCondition(c, stepResults, stepErrors),
|
|
411
|
-
)
|
|
412
|
-
if (cond.type === 'or')
|
|
413
|
-
return (cond.conds ?? []).some((c) =>
|
|
414
|
-
evaluateCondition(c, stepResults, stepErrors),
|
|
415
|
-
)
|
|
416
|
-
return true
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// ── Request handlers ─────────────────────────────────────────────────────
|
|
420
|
-
|
|
421
|
-
function handleExecute(
|
|
422
|
-
database: Database.Database,
|
|
423
|
-
req: HranaRequest,
|
|
424
|
-
sqlStore: Map<number, string>,
|
|
425
|
-
) {
|
|
426
|
-
if (!req.stmt)
|
|
427
|
-
return {
|
|
428
|
-
type: 'error' as const,
|
|
429
|
-
error: { message: 'Missing stmt', code: 'HRANA_PROTO_ERROR' },
|
|
430
|
-
}
|
|
431
|
-
const result = errore.try({
|
|
432
|
-
try: () => executeStmt(database, req.stmt!, sqlStore),
|
|
433
|
-
catch: (e) => e as Error,
|
|
434
|
-
})
|
|
435
|
-
if (result instanceof Error) {
|
|
436
|
-
return {
|
|
437
|
-
type: 'error' as const,
|
|
438
|
-
error: { message: result.message, code: getSqliteErrorCode(result) },
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
return { type: 'ok' as const, response: { type: 'execute', result } }
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
function handleBatch(
|
|
445
|
-
database: Database.Database,
|
|
446
|
-
req: HranaRequest,
|
|
447
|
-
sqlStore: Map<number, string>,
|
|
448
|
-
) {
|
|
449
|
-
const steps = req.batch?.steps ?? []
|
|
450
|
-
const stepResults: Array<HranaExecuteResult | null> = []
|
|
451
|
-
const stepErrors: Array<{ message: string; code: string } | null> = []
|
|
452
|
-
|
|
453
|
-
for (const step of steps) {
|
|
454
|
-
if (!evaluateCondition(step.condition, stepResults, stepErrors)) {
|
|
455
|
-
stepResults.push(null)
|
|
456
|
-
stepErrors.push(null)
|
|
457
|
-
continue
|
|
458
|
-
}
|
|
459
|
-
const result = errore.try({
|
|
460
|
-
try: () => executeStmt(database, step.stmt, sqlStore),
|
|
461
|
-
catch: (e) => e as Error,
|
|
462
|
-
})
|
|
463
|
-
if (result instanceof Error) {
|
|
464
|
-
stepResults.push(null)
|
|
465
|
-
stepErrors.push({
|
|
466
|
-
message: result.message,
|
|
467
|
-
code: getSqliteErrorCode(result),
|
|
468
|
-
})
|
|
469
|
-
} else {
|
|
470
|
-
stepResults.push(result)
|
|
471
|
-
stepErrors.push(null)
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
return {
|
|
476
|
-
type: 'ok' as const,
|
|
477
|
-
response: {
|
|
478
|
-
type: 'batch',
|
|
479
|
-
result: { step_results: stepResults, step_errors: stepErrors },
|
|
480
|
-
},
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
function handleSequence(
|
|
485
|
-
database: Database.Database,
|
|
486
|
-
req: HranaRequest,
|
|
487
|
-
sqlStore: Map<number, string>,
|
|
488
|
-
) {
|
|
489
|
-
const sql = req.sql ?? (req.sql_id != null ? sqlStore.get(req.sql_id) : null)
|
|
490
|
-
if (!sql) return { type: 'ok' as const, response: { type: 'sequence' } }
|
|
491
|
-
const result = errore.try({
|
|
492
|
-
try: () => {
|
|
493
|
-
database.exec(sql)
|
|
494
|
-
},
|
|
495
|
-
catch: (e) => e as Error,
|
|
496
|
-
})
|
|
497
|
-
if (result instanceof Error) {
|
|
498
|
-
return {
|
|
499
|
-
type: 'error' as const,
|
|
500
|
-
error: { message: result.message, code: getSqliteErrorCode(result) },
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
return { type: 'ok' as const, response: { type: 'sequence' } }
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function processRequest(
|
|
507
|
-
database: Database.Database,
|
|
508
|
-
req: HranaRequest,
|
|
509
|
-
sqlStore: Map<number, string>,
|
|
510
|
-
) {
|
|
511
|
-
if (req.type === 'execute') return handleExecute(database, req, sqlStore)
|
|
512
|
-
if (req.type === 'batch') return handleBatch(database, req, sqlStore)
|
|
513
|
-
if (req.type === 'sequence') return handleSequence(database, req, sqlStore)
|
|
514
|
-
if (req.type === 'close')
|
|
515
|
-
return { type: 'ok' as const, response: { type: 'close' } }
|
|
516
|
-
if (req.type === 'store_sql') {
|
|
517
|
-
if (req.sql_id != null && req.sql != null) sqlStore.set(req.sql_id, req.sql)
|
|
518
|
-
return { type: 'ok' as const, response: { type: 'store_sql' } }
|
|
519
|
-
}
|
|
520
|
-
if (req.type === 'close_sql') {
|
|
521
|
-
if (req.sql_id != null) sqlStore.delete(req.sql_id)
|
|
522
|
-
return { type: 'ok' as const, response: { type: 'close_sql' } }
|
|
523
|
-
}
|
|
524
|
-
return {
|
|
525
|
-
type: 'error' as const,
|
|
526
|
-
error: {
|
|
527
|
-
message: `Unknown request type: ${req.type}`,
|
|
528
|
-
code: 'HRANA_PROTO_ERROR',
|
|
529
|
-
},
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// ── HTTP handler ─────────────────────────────────────────────────────────
|
|
534
|
-
|
|
535
|
-
// @libsql/client HTTP driver uses batons to keep streams alive across
|
|
536
|
-
// pipeline requests (needed for interactive transactions). Each stream has
|
|
537
|
-
// its own SQL store for store_sql/close_sql scoping.
|
|
538
|
-
|
|
539
|
-
let batonCounter = 0
|
|
540
|
-
const streamStores = new Map<string, Map<number, string>>()
|
|
541
|
-
|
|
542
|
-
export function createHranaHandler(
|
|
543
|
-
database: Database.Database,
|
|
544
|
-
): http.RequestListener {
|
|
545
|
-
return (req, res) => {
|
|
546
|
-
const requestUrl = new URL(req.url || '/', 'http://127.0.0.1')
|
|
547
|
-
|
|
548
|
-
if (req.method === 'GET' && requestUrl.pathname === '/health') {
|
|
549
|
-
res.writeHead(200, { 'content-type': 'application/json' })
|
|
550
|
-
res.end(JSON.stringify({ status: 'ok', pid: process.pid }))
|
|
551
|
-
return
|
|
552
|
-
}
|
|
553
|
-
if (req.method === 'GET' && requestUrl.pathname === '/v2') {
|
|
554
|
-
res.writeHead(200, { 'content-type': 'application/json' })
|
|
555
|
-
res.end('{"version":"hrana-v2"}')
|
|
556
|
-
return
|
|
557
|
-
}
|
|
558
|
-
if (req.method === 'POST' && requestUrl.pathname === '/v2/pipeline') {
|
|
559
|
-
const chunks: Buffer[] = []
|
|
560
|
-
let aborted = false
|
|
561
|
-
req.on('error', () => {
|
|
562
|
-
aborted = true
|
|
563
|
-
res.destroy()
|
|
564
|
-
})
|
|
565
|
-
req.on('data', (chunk: Buffer) => {
|
|
566
|
-
chunks.push(chunk)
|
|
567
|
-
})
|
|
568
|
-
req.on('end', () => {
|
|
569
|
-
if (aborted) return
|
|
570
|
-
const parseResult = errore.try({
|
|
571
|
-
try: () =>
|
|
572
|
-
JSON.parse(
|
|
573
|
-
Buffer.concat(chunks).toString(),
|
|
574
|
-
) as HranaPipelineRequest,
|
|
575
|
-
catch: (e) => e as Error,
|
|
576
|
-
})
|
|
577
|
-
if (parseResult instanceof Error) {
|
|
578
|
-
res.writeHead(400, { 'content-type': 'application/json' })
|
|
579
|
-
res.end(
|
|
580
|
-
JSON.stringify({
|
|
581
|
-
error: {
|
|
582
|
-
message: parseResult.message,
|
|
583
|
-
code: 'HRANA_PROTO_ERROR',
|
|
584
|
-
},
|
|
585
|
-
}),
|
|
586
|
-
)
|
|
587
|
-
return
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// Resolve or create per-stream SQL store keyed by baton
|
|
591
|
-
const incoming = parseResult.baton
|
|
592
|
-
const sqlStore =
|
|
593
|
-
(incoming ? streamStores.get(incoming) : undefined) ??
|
|
594
|
-
new Map<number, string>()
|
|
595
|
-
if (incoming) streamStores.delete(incoming)
|
|
596
|
-
|
|
597
|
-
const results = (parseResult.requests ?? []).map((r) =>
|
|
598
|
-
processRequest(database, r, sqlStore),
|
|
599
|
-
)
|
|
600
|
-
const hasClose = (parseResult.requests ?? []).some(
|
|
601
|
-
(r) => r.type === 'close',
|
|
602
|
-
)
|
|
603
|
-
|
|
604
|
-
const baton = hasClose ? null : `b${++batonCounter}`
|
|
605
|
-
if (baton) streamStores.set(baton, sqlStore)
|
|
606
|
-
|
|
607
|
-
res.writeHead(200, { 'content-type': 'application/json' })
|
|
608
|
-
res.end(JSON.stringify({ baton, base_url: null, results }))
|
|
609
|
-
})
|
|
610
|
-
return
|
|
611
|
-
}
|
|
612
|
-
res.writeHead(404)
|
|
613
|
-
res.end()
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// ── Single-instance enforcement ──────────────────────────────────────────
|
|
245
|
+
// ── Single-instance enforcement ──────────────────────────────────────
|
|
618
246
|
|
|
619
247
|
/**
|
|
620
248
|
* Evict a previous kimaki instance on the lock port.
|
|
@@ -46,6 +46,7 @@ test(
|
|
|
46
46
|
const port = chooseLockPort({ key: 'opencode-plugin-loading-e2e' })
|
|
47
47
|
const pluginPath = new URL('../src/kimaki-opencode-plugin.ts', import.meta.url).href
|
|
48
48
|
const stderrLines: string[] = []
|
|
49
|
+
const isolatedOpencodeRoot = path.join(projectDir, 'opencode-test-home')
|
|
49
50
|
|
|
50
51
|
const {
|
|
51
52
|
command,
|
|
@@ -68,6 +69,12 @@ test(
|
|
|
68
69
|
formatter: false,
|
|
69
70
|
plugin: [pluginPath],
|
|
70
71
|
}),
|
|
72
|
+
OPENCODE_TEST_HOME: isolatedOpencodeRoot,
|
|
73
|
+
OPENCODE_CONFIG_DIR: path.join(isolatedOpencodeRoot, '.opencode-kimaki'),
|
|
74
|
+
XDG_CONFIG_HOME: path.join(isolatedOpencodeRoot, '.config'),
|
|
75
|
+
XDG_DATA_HOME: path.join(isolatedOpencodeRoot, '.local', 'share'),
|
|
76
|
+
XDG_CACHE_HOME: path.join(isolatedOpencodeRoot, '.cache'),
|
|
77
|
+
XDG_STATE_HOME: path.join(isolatedOpencodeRoot, '.local', 'state'),
|
|
71
78
|
},
|
|
72
79
|
})
|
|
73
80
|
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
// - ipc-tools-plugin: file upload + action buttons (IPC-based Discord tools)
|
|
8
8
|
// - context-awareness-plugin: branch, pwd, memory, time gap, onboarding tutorial
|
|
9
9
|
// - opencode-interrupt-plugin: interrupt queued messages at step boundaries
|
|
10
|
+
// - kitty-graphics-plugin: extract Kitty Graphics Protocol images from bash output
|
|
10
11
|
|
|
11
12
|
export { ipcToolsPlugin } from './ipc-tools-plugin.js'
|
|
12
13
|
export { contextAwarenessPlugin } from './context-awareness-plugin.js'
|
|
13
14
|
export { interruptOpencodeSessionOnUserMessage } from './opencode-interrupt-plugin.js'
|
|
14
15
|
export { anthropicAuthPlugin } from './anthropic-auth-plugin.js'
|
|
16
|
+
export { kittyGraphicsPlugin } from 'kitty-graphics-agent'
|
package/src/markdown.test.ts
CHANGED
|
@@ -131,7 +131,7 @@ beforeAll(async () => {
|
|
|
131
131
|
const msgs = await client.session.messages({ sessionID })
|
|
132
132
|
const assistantMsg = msgs.data?.find((m) => m.info.role === 'assistant')
|
|
133
133
|
const hasTextParts = assistantMsg?.parts?.some((p) => {
|
|
134
|
-
return p.type === 'text' &&
|
|
134
|
+
return p.type === 'text' && p.text && !p.synthetic
|
|
135
135
|
})
|
|
136
136
|
if (hasTextParts) {
|
|
137
137
|
// Extra wait for step-start and other parts to be flushed
|
package/src/markdown.ts
CHANGED
|
@@ -315,8 +315,8 @@ export function getCompactSessionContext({
|
|
|
315
315
|
for (const msg of recentMessages) {
|
|
316
316
|
if (msg.info.role === 'user') {
|
|
317
317
|
const textParts = (msg.parts || [])
|
|
318
|
-
.filter((p) => p.type === 'text'
|
|
319
|
-
.map((p) => ('text'
|
|
318
|
+
.filter((p) => p.type === 'text')
|
|
319
|
+
.map((p) => (p.type === 'text' ? extractNonXmlContent(p.text || '') : ''))
|
|
320
320
|
.filter(Boolean)
|
|
321
321
|
if (textParts.length > 0) {
|
|
322
322
|
lines.push(`[User]: ${textParts.join(' ').slice(0, 1000)}`)
|
|
@@ -326,9 +326,9 @@ export function getCompactSessionContext({
|
|
|
326
326
|
// Get assistant text parts (non-synthetic, non-empty)
|
|
327
327
|
const textParts = (msg.parts || [])
|
|
328
328
|
.filter(
|
|
329
|
-
(p) => p.type === 'text' &&
|
|
329
|
+
(p) => p.type === 'text' && !p.synthetic && p.text,
|
|
330
330
|
)
|
|
331
|
-
.map((p) => ('text'
|
|
331
|
+
.map((p) => (p.type === 'text' ? p.text : ''))
|
|
332
332
|
.filter(Boolean)
|
|
333
333
|
if (textParts.length > 0) {
|
|
334
334
|
lines.push(`[Assistant]: ${textParts.join(' ').slice(0, 1000)}`)
|
|
@@ -71,36 +71,85 @@ function normalizeWhitespace(text: string): string {
|
|
|
71
71
|
return text.replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' ')
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
// A chunk of formatted content with associated part IDs, ready to be
|
|
75
|
+
// batched into as few Discord messages as possible.
|
|
76
|
+
export type SessionChunk = {
|
|
77
|
+
partIds: string[]
|
|
78
|
+
content: string
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
82
|
+
* Collect renderable assistant parts from session messages as SessionChunks.
|
|
83
|
+
* Each non-empty formatted part becomes one chunk. Caller can batch them
|
|
84
|
+
* with batchChunksForDiscord() before sending.
|
|
85
|
+
*
|
|
86
|
+
* - skipPartIds: parts already synced (external sync). Skipped parts are
|
|
87
|
+
* not included in the result.
|
|
88
|
+
* - limit: max parts to include (from the end). Older parts are counted
|
|
89
|
+
* in skippedCount.
|
|
77
90
|
*/
|
|
78
|
-
export function
|
|
91
|
+
export function collectSessionChunks({
|
|
79
92
|
messages,
|
|
80
|
-
|
|
93
|
+
skipPartIds,
|
|
94
|
+
limit,
|
|
81
95
|
}: {
|
|
82
96
|
messages: GenericSessionMessage[]
|
|
97
|
+
skipPartIds?: Set<string>
|
|
83
98
|
limit?: number
|
|
84
|
-
}): {
|
|
85
|
-
const
|
|
99
|
+
}): { chunks: SessionChunk[]; skippedCount: number } {
|
|
100
|
+
const allChunks: SessionChunk[] = []
|
|
86
101
|
|
|
87
102
|
for (const message of messages) {
|
|
88
|
-
if (message.info.role
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
if (message.info.role !== 'assistant') {
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
for (const part of message.parts) {
|
|
107
|
+
if (skipPartIds?.has(part.id)) {
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
const content = formatPart(part)
|
|
111
|
+
if (!content.trim()) {
|
|
112
|
+
continue
|
|
94
113
|
}
|
|
114
|
+
allChunks.push({ partIds: [part.id], content: content.trimEnd() })
|
|
95
115
|
}
|
|
96
116
|
}
|
|
97
117
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
118
|
+
if (limit !== undefined && allChunks.length > limit) {
|
|
119
|
+
return {
|
|
120
|
+
chunks: allChunks.slice(-limit),
|
|
121
|
+
skippedCount: allChunks.length - limit,
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { chunks: allChunks, skippedCount: 0 }
|
|
125
|
+
}
|
|
102
126
|
|
|
103
|
-
|
|
127
|
+
// Merge consecutive SessionChunks into as few Discord messages as possible,
|
|
128
|
+
// respecting the 2000 char limit.
|
|
129
|
+
const DISCORD_BATCH_MAX_LENGTH = 2000
|
|
130
|
+
|
|
131
|
+
export function batchChunksForDiscord(chunks: SessionChunk[]): SessionChunk[] {
|
|
132
|
+
if (chunks.length === 0) {
|
|
133
|
+
return []
|
|
134
|
+
}
|
|
135
|
+
const batched: SessionChunk[] = []
|
|
136
|
+
let current: SessionChunk = { partIds: [...chunks[0]!.partIds], content: chunks[0]!.content }
|
|
137
|
+
|
|
138
|
+
for (let i = 1; i < chunks.length; i++) {
|
|
139
|
+
const next = chunks[i]!
|
|
140
|
+
const merged = current.content + '\n' + next.content
|
|
141
|
+
if (merged.length <= DISCORD_BATCH_MAX_LENGTH) {
|
|
142
|
+
current = {
|
|
143
|
+
partIds: [...current.partIds, ...next.partIds],
|
|
144
|
+
content: merged,
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
batched.push(current)
|
|
148
|
+
current = { partIds: [...next.partIds], content: next.content }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
batched.push(current)
|
|
152
|
+
return batched
|
|
104
153
|
}
|
|
105
154
|
|
|
106
155
|
export const TEXT_MIME_TYPES = [
|
|
@@ -146,7 +146,7 @@ ${backticks}bash
|
|
|
146
146
|
PORT=$((RANDOM % 6000 + 3000))
|
|
147
147
|
tmux kill-session -t game-dev 2>/dev/null
|
|
148
148
|
tmux new-session -d -s game-dev -c "$PWD"
|
|
149
|
-
tmux send-keys -t game-dev "PORT=$PORT kimaki tunnel -p $PORT -- bun run server.ts" Enter
|
|
149
|
+
tmux send-keys -t game-dev "PORT=$PORT kimaki tunnel --kill -p $PORT -- bun run server.ts" Enter
|
|
150
150
|
${backticks}
|
|
151
151
|
|
|
152
152
|
Wait a moment, then get the tunnel URL:
|
package/src/openai-realtime.ts
CHANGED
|
@@ -250,15 +250,13 @@ export async function startGenAiSession({
|
|
|
250
250
|
'conversation.item.created',
|
|
251
251
|
({ item }: { item: ConversationItem }) => {
|
|
252
252
|
if (
|
|
253
|
-
'role' in item &&
|
|
254
253
|
item.role === 'assistant' &&
|
|
255
254
|
item.type === 'message'
|
|
256
255
|
) {
|
|
257
256
|
// Check if this is the first audio content
|
|
258
257
|
const hasAudio =
|
|
259
|
-
'content' in item &&
|
|
260
258
|
Array.isArray(item.content) &&
|
|
261
|
-
item.content.some((c) =>
|
|
259
|
+
item.content.some((c) => c.type === 'audio')
|
|
262
260
|
if (hasAudio && !isAssistantSpeaking && onAssistantStartSpeaking) {
|
|
263
261
|
isAssistantSpeaking = true
|
|
264
262
|
onAssistantStartSpeaking()
|
|
@@ -277,7 +275,7 @@ export async function startGenAiSession({
|
|
|
277
275
|
delta: ConversationEventDelta | null
|
|
278
276
|
}) => {
|
|
279
277
|
// Handle audio chunks
|
|
280
|
-
if (delta?.audio &&
|
|
278
|
+
if (delta?.audio && item.role === 'assistant') {
|
|
281
279
|
if (!isAssistantSpeaking && onAssistantStartSpeaking) {
|
|
282
280
|
isAssistantSpeaking = true
|
|
283
281
|
onAssistantStartSpeaking()
|
|
@@ -301,12 +299,10 @@ export async function startGenAiSession({
|
|
|
301
299
|
|
|
302
300
|
// Handle transcriptions
|
|
303
301
|
if (delta?.transcript) {
|
|
304
|
-
if (
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
openaiLogger.log('Assistant transcription:', delta.transcript)
|
|
309
|
-
}
|
|
302
|
+
if (item.role === 'user') {
|
|
303
|
+
openaiLogger.log('User transcription:', delta.transcript)
|
|
304
|
+
} else if (item.role === 'assistant') {
|
|
305
|
+
openaiLogger.log('Assistant transcription:', delta.transcript)
|
|
310
306
|
}
|
|
311
307
|
}
|
|
312
308
|
},
|