repoburg 1.3.199 → 1.3.202
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/agent-pane/backend/data.db +0 -0
- package/agent-pane/backend/data.db-shm +0 -0
- package/agent-pane/backend/data.db-wal +0 -0
- package/agent-pane/backend/dist/index.js +1658 -277
- package/agent-pane/backend/src/claude/claude-provider.test.ts +393 -3
- package/agent-pane/backend/src/claude/claude-provider.ts +53 -50
- package/agent-pane/backend/src/db/index.ts +12 -0
- package/agent-pane/backend/src/index.ts +5 -2
- package/agent-pane/backend/src/integration/threads-claude-fork.test.ts +241 -0
- package/agent-pane/backend/src/providers/index.ts +3 -1
- package/agent-pane/backend/src/providers/runtime-provider.ts +6 -0
- package/agent-pane/backend/src/repoburg/action-mapping.test.ts +122 -0
- package/agent-pane/backend/src/repoburg/action-mapping.ts +294 -0
- package/agent-pane/backend/src/repoburg/agent-runtime-client.test.ts +271 -0
- package/agent-pane/backend/src/repoburg/agent-runtime-client.ts +285 -0
- package/agent-pane/backend/src/repoburg/backend-resolver.test.ts +356 -0
- package/agent-pane/backend/src/repoburg/backend-resolver.ts +261 -0
- package/agent-pane/backend/src/repoburg/repoburg-client.test.ts +180 -0
- package/agent-pane/backend/src/repoburg/repoburg-client.ts +194 -0
- package/agent-pane/backend/src/repoburg/repoburg-provider.test.ts +780 -0
- package/agent-pane/backend/src/repoburg/repoburg-provider.ts +471 -0
- package/agent-pane/backend/src/repoburg/turn-projector.test.ts +621 -0
- package/agent-pane/backend/src/repoburg/turn-projector.ts +637 -0
- package/agent-pane/backend/src/routes/providers.test.ts +18 -1
- package/agent-pane/backend/src/routes/providers.ts +19 -1
- package/agent-pane/backend/src/routes/sessions.test.ts +215 -1
- package/agent-pane/backend/src/routes/sessions.ts +82 -9
- package/agent-pane/backend/src/routes/threads.ts +21 -6
- package/agent-pane/backend/src/routes/turns.test.ts +45 -1
- package/agent-pane/frontend/dist/assets/index-CJxD4YmJ.js +370 -0
- package/agent-pane/frontend/dist/assets/{index-Dwwhx7YN.css → index-r-xwfOKQ.css} +1 -1
- package/agent-pane/frontend/dist/index.html +2 -2
- package/agent-pane/frontend/src/api/sessions.test.ts +1 -0
- package/agent-pane/frontend/src/api/websocket.test.ts +2 -2
- package/agent-pane/frontend/src/api/websocket.ts +3 -1
- package/agent-pane/frontend/src/components/AskUserItem.test.tsx +143 -0
- package/agent-pane/frontend/src/components/AskUserItem.tsx +207 -0
- package/agent-pane/frontend/src/components/CommentableText.test.tsx +196 -0
- package/agent-pane/frontend/src/components/CommentableText.tsx +109 -21
- package/agent-pane/frontend/src/components/IdeWorkspace.test.tsx +39 -0
- package/agent-pane/frontend/src/components/IdeWorkspace.tsx +28 -2
- package/agent-pane/frontend/src/components/ResizeHandle.tsx +29 -0
- package/agent-pane/frontend/src/components/TurnContent.test.tsx +79 -0
- package/agent-pane/frontend/src/components/TurnContent.tsx +49 -3
- package/agent-pane/frontend/src/components/panels/ThreadsPanel.tsx +35 -3
- package/agent-pane/frontend/src/components/panels/TurnsPanel.test.tsx +48 -0
- package/agent-pane/frontend/src/components/panels/TurnsPanel.tsx +7 -2
- package/agent-pane/frontend/src/constants/models.ts +37 -0
- package/agent-pane/frontend/src/hooks/useResizable.test.ts +97 -0
- package/agent-pane/frontend/src/hooks/useResizable.ts +75 -0
- package/agent-pane/frontend/src/store/store.test.ts +54 -0
- package/agent-pane/frontend/src/store/uiSlice.ts +50 -0
- package/agent-pane/frontend/src/store/wsSlice.ts +10 -3
- package/agent-pane/frontend/src/styles/app.css +26 -1
- package/agent-pane/frontend/src/test/component-helpers.ts +2 -0
- package/agent-pane/frontend/src/test/fixtures.ts +1 -0
- package/agent-pane/frontend/src/types/index.ts +2 -0
- package/agent-pane/frontend/src/utils/duration.test.ts +1 -0
- package/agent-pane/shared/types.ts +17 -0
- package/agent-pane-bug-repro-no-agent-reply.png +0 -0
- package/askuser-after-step5-postgres-selected.png +0 -0
- package/askuser-after-step7-answer-sent.png +0 -0
- package/askuser-card-visible.png +0 -0
- package/backend/dist/src/agent-runtime/agent-runtime-event-emitter.d.ts +36 -0
- package/backend/dist/src/agent-runtime/agent-runtime-event-emitter.js +199 -0
- package/backend/dist/src/agent-runtime/agent-runtime-event-emitter.js.map +1 -0
- package/backend/dist/src/agent-runtime/agent-runtime.controller.d.ts +11 -0
- package/backend/dist/src/agent-runtime/agent-runtime.controller.js +91 -0
- package/backend/dist/src/agent-runtime/agent-runtime.controller.js.map +1 -0
- package/backend/dist/src/agent-runtime/agent-runtime.module.d.ts +2 -0
- package/backend/dist/src/agent-runtime/agent-runtime.module.js +26 -0
- package/backend/dist/src/agent-runtime/agent-runtime.module.js.map +1 -0
- package/backend/dist/src/agent-runtime/agent-runtime.types.d.ts +92 -0
- package/backend/dist/src/agent-runtime/agent-runtime.types.js +3 -0
- package/backend/dist/src/agent-runtime/agent-runtime.types.js.map +1 -0
- package/backend/dist/src/app.controller.d.ts +1 -0
- package/backend/dist/src/app.module.js +4 -0
- package/backend/dist/src/app.module.js.map +1 -1
- package/backend/dist/src/app.service.d.ts +1 -0
- package/backend/dist/src/app.service.js +4 -1
- package/backend/dist/src/app.service.js.map +1 -1
- package/backend/dist/src/interactive-chat/chat.module.js +4 -0
- package/backend/dist/src/interactive-chat/chat.module.js.map +1 -1
- package/backend/dist/src/interactive-chat/chat.service.d.ts +7 -1
- package/backend/dist/src/interactive-chat/chat.service.js +54 -7
- package/backend/dist/src/interactive-chat/chat.service.js.map +1 -1
- package/backend/dist/src/llm-orchestration/action-handlers/invoke-subagent.handler.js +12 -1
- package/backend/dist/src/llm-orchestration/action-handlers/invoke-subagent.handler.js.map +1 -1
- package/backend/dist/src/llm-orchestration/hooks/frontend-notification.hook.d.ts +3 -1
- package/backend/dist/src/llm-orchestration/hooks/frontend-notification.hook.js +20 -2
- package/backend/dist/src/llm-orchestration/hooks/frontend-notification.hook.js.map +1 -1
- package/backend/dist/src/llm-orchestration/llm-orchestration.interfaces.d.ts +1 -0
- package/backend/dist/src/llm-orchestration/llm-orchestration.interfaces.js +1 -0
- package/backend/dist/src/llm-orchestration/llm-orchestration.interfaces.js.map +1 -1
- package/backend/dist/src/llm-orchestration/llm-orchestration.module.js +2 -0
- package/backend/dist/src/llm-orchestration/llm-orchestration.module.js.map +1 -1
- package/backend/dist/src/llm-orchestration/llm-turn-processor.service.d.ts +4 -1
- package/backend/dist/src/llm-orchestration/llm-turn-processor.service.js +37 -2
- package/backend/dist/src/llm-orchestration/llm-turn-processor.service.js.map +1 -1
- package/backend/dist/src/llm-provider/ollama-llm.provider.d.ts +1 -0
- package/backend/dist/src/llm-provider/ollama-llm.provider.js +19 -1
- package/backend/dist/src/llm-provider/ollama-llm.provider.js.map +1 -1
- package/backend/dist/src/llm-responses/llm-responses.module.js +4 -0
- package/backend/dist/src/llm-responses/llm-responses.module.js.map +1 -1
- package/backend/dist/src/llm-responses/llm-responses.service.d.ts +4 -1
- package/backend/dist/src/llm-responses/llm-responses.service.js +52 -2
- package/backend/dist/src/llm-responses/llm-responses.service.js.map +1 -1
- package/backend/dist/tsconfig.build.tsbuildinfo +1 -1
- package/daemon/dist/api/services.js +4 -0
- package/daemon/dist/index.js +0 -0
- package/daemon/dist/serviceManager.js +30 -11
- package/daemon/dist/serviceManager.spec.js +26 -0
- package/daemon/dist/workspace-validation.js +63 -0
- package/daemon/dist/workspace-validation.spec.js +63 -0
- package/package.json +1 -1
- package/r2-step4-apple-banana.png +0 -0
- package/r2-step5-after-rewind-apple-last.png +0 -0
- package/r2-step7-apple-cherry.png +0 -0
- package/r2-step8-cherry-menu-no-restore.png +0 -0
- package/r3-step3-live-task-card-running.png +0 -0
- package/r3-step4-after-completion.png +0 -0
- package/r3-step5-persistence-after-reload.png +0 -0
- package/r3-step6-tasks-tab-card.png +0 -0
- package/reverify-answered-state-answer-sent.png +0 -0
- package/reverify-card-visible-after-completion.png +0 -0
- package/rewind-step3-apple-banana-turns.png +0 -0
- package/rewind-step5-ui-unchanged-after-confirmed-rewind.png +0 -0
- package/rewind-step6-apple-menu-no-restore.png +0 -0
- package/rewind-step7-final-turnlist-unchanged.png +0 -0
- package/rewind-step7-negative-error-toast.png +0 -0
- package/rewind-step7-negative-error-toast2.png +0 -0
- package/subagent-r2-step5-turn-after-reload-no-card.png +0 -0
- package/subagent-r2-step6-tasks-panel-card.png +0 -0
- package/subagent-step3-tasks-tab-done-card.png +0 -0
- package/subagent-step4-turn-stuck-no-cards.png +0 -0
- package/agent-pane/frontend/dist/assets/index-BYo-lnxm.js +0 -362
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { RepoburgHttpClient } from './repoburg-client.ts';
|
|
3
|
+
|
|
4
|
+
describe('RepoburgHttpClient (HTTP control surface)', () => {
|
|
5
|
+
const client = new RepoburgHttpClient('http://repoburg.test');
|
|
6
|
+
|
|
7
|
+
const fetchMock = vi.fn();
|
|
8
|
+
const originalFetch = globalThis.fetch;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
vi.clearAllMocks();
|
|
12
|
+
globalThis.fetch = fetchMock as never;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
globalThis.fetch = originalFetch;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
function ok(body: unknown): Response {
|
|
20
|
+
return { ok: true, status: 200, text: () => Promise.resolve(JSON.stringify(body)) } as never;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function error(status: number, body: unknown): Response {
|
|
24
|
+
const text = typeof body === 'string' ? body : JSON.stringify(body);
|
|
25
|
+
return { ok: false, status, text: () => Promise.resolve(text) } as never;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
it('createSession POSTs the DTO', async () => {
|
|
29
|
+
fetchMock.mockResolvedValue(ok({ id: 'rs-1' }));
|
|
30
|
+
|
|
31
|
+
const session = await client.createSession({
|
|
32
|
+
session_title: 'T',
|
|
33
|
+
model_id: 'ollama/glm-5.3:cloud',
|
|
34
|
+
reasoning_effort: 'high',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(session.id).toBe('rs-1');
|
|
38
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
39
|
+
'http://repoburg.test/sessions',
|
|
40
|
+
expect.objectContaining({
|
|
41
|
+
method: 'POST',
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
session_title: 'T',
|
|
44
|
+
model_id: 'ollama/glm-5.3:cloud',
|
|
45
|
+
reasoning_effort: 'high',
|
|
46
|
+
}),
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('patchSession PATCHes the DTO', async () => {
|
|
52
|
+
fetchMock.mockResolvedValue(ok({ id: 'rs-1' }));
|
|
53
|
+
|
|
54
|
+
await client.patchSession('rs-1', { model_id: 'ollama/b' });
|
|
55
|
+
|
|
56
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
57
|
+
'http://repoburg.test/sessions/rs-1',
|
|
58
|
+
expect.objectContaining({
|
|
59
|
+
method: 'PATCH',
|
|
60
|
+
body: JSON.stringify({ model_id: 'ollama/b' }),
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('getSession returns the session incl. is_running', async () => {
|
|
66
|
+
fetchMock.mockResolvedValue(ok({ id: 'rs-1', session_title: 'T', is_running: true }));
|
|
67
|
+
|
|
68
|
+
const session = await client.getSession('rs-1');
|
|
69
|
+
|
|
70
|
+
expect(session).toEqual({ id: 'rs-1', session_title: 'T', is_running: true });
|
|
71
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
72
|
+
'http://repoburg.test/sessions/rs-1',
|
|
73
|
+
expect.objectContaining({ method: 'GET', body: undefined }),
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('createInput POSTs user_prompt + execution_strategy', async () => {
|
|
78
|
+
fetchMock.mockResolvedValue(ok({ id: 'in-1' }));
|
|
79
|
+
|
|
80
|
+
await client.createInput('rs-1', {
|
|
81
|
+
user_prompt: 'hello',
|
|
82
|
+
execution_strategy: 'auto_apply',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
86
|
+
'http://repoburg.test/sessions/rs-1/inputs',
|
|
87
|
+
expect.objectContaining({
|
|
88
|
+
method: 'POST',
|
|
89
|
+
body: JSON.stringify({ user_prompt: 'hello', execution_strategy: 'auto_apply' }),
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('getSessionInputs GETs the full snapshot', async () => {
|
|
95
|
+
fetchMock.mockResolvedValue(ok([{ id: 'in-1', role: 'user' }, { id: 'in-2', role: 'model' }]));
|
|
96
|
+
|
|
97
|
+
const inputs = await client.getSessionInputs('rs-1');
|
|
98
|
+
|
|
99
|
+
expect(inputs).toHaveLength(2);
|
|
100
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
101
|
+
'http://repoburg.test/sessions/rs-1/inputs',
|
|
102
|
+
expect.objectContaining({ method: 'GET', body: undefined }),
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('cancel POSTs the streaming cancel endpoint', async () => {
|
|
107
|
+
fetchMock.mockResolvedValue(ok({ success: true, cancelled: true }));
|
|
108
|
+
|
|
109
|
+
const result = await client.cancel('rs-1');
|
|
110
|
+
|
|
111
|
+
expect(result).toEqual({ success: true, cancelled: true });
|
|
112
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
113
|
+
'http://repoburg.test/streaming/rs-1/cancel',
|
|
114
|
+
expect.objectContaining({ method: 'POST', body: undefined }),
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('getModels unwraps the {models} envelope', async () => {
|
|
119
|
+
fetchMock.mockResolvedValue(ok({ models: [{ id: 'ollama/glm-5.3:cloud', name: 'glm-5.3:cloud' }] }));
|
|
120
|
+
|
|
121
|
+
const models = await client.getModels();
|
|
122
|
+
|
|
123
|
+
expect(models).toEqual([{ id: 'ollama/glm-5.3:cloud', name: 'glm-5.3:cloud' }]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('getModels tolerates a missing envelope', async () => {
|
|
127
|
+
fetchMock.mockResolvedValue(ok({}));
|
|
128
|
+
|
|
129
|
+
const models = await client.getModels();
|
|
130
|
+
|
|
131
|
+
expect(models).toEqual([]);
|
|
132
|
+
});
|
|
133
|
+
it('deleteInputsUntilHere DELETEs the anchor until-here endpoint (204 empty body)', async () => {
|
|
134
|
+
// The root endpoint replies 204 No Content — must resolve, not throw.
|
|
135
|
+
fetchMock.mockResolvedValue({ ok: true, status: 204, text: () => Promise.resolve('') } as never);
|
|
136
|
+
|
|
137
|
+
await client.deleteInputsUntilHere('rs-1', 'in-9');
|
|
138
|
+
|
|
139
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
140
|
+
'http://repoburg.test/sessions/rs-1/inputs/in-9/until-here',
|
|
141
|
+
expect.objectContaining({ method: 'DELETE', body: undefined }),
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('deleteInputsUntilHere surfaces the 400 summary-in-range error', async () => {
|
|
146
|
+
fetchMock.mockResolvedValue(
|
|
147
|
+
error(400, { message: 'Summaries can only be discarded, not deleted.' }),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
await expect(client.deleteInputsUntilHere('rs-1', 'in-9')).rejects.toThrow(
|
|
151
|
+
'DELETE /sessions/rs-1/inputs/in-9/until-here failed: 400 {"message":"Summaries can only be discarded, not deleted."}',
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('throws with status + body text on non-OK responses', async () => {
|
|
156
|
+
fetchMock.mockResolvedValue(error(400, 'Bad request'));
|
|
157
|
+
|
|
158
|
+
await expect(client.createInput('rs-1', { user_prompt: 'x', execution_strategy: 'auto_apply' }))
|
|
159
|
+
.rejects.toThrow('POST /sessions/rs-1/inputs failed: 400 Bad request');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('detects HTML error pages (wrong server) with a clear message', async () => {
|
|
163
|
+
fetchMock.mockResolvedValue(error(500, '<!doctype html><html><body>500</body></html>'));
|
|
164
|
+
|
|
165
|
+
await expect(client.getSession('rs-1')).rejects.toThrow(
|
|
166
|
+
/HTML response from http:\/\/repoburg\.test — the URL points to the wrong server/,
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('detects non-JSON success responses (wrong server)', async () => {
|
|
171
|
+
fetchMock.mockResolvedValue(
|
|
172
|
+
ok(undefined) as never,
|
|
173
|
+
);
|
|
174
|
+
fetchMock.mockResolvedValue({ ok: true, status: 200, text: () => Promise.resolve('not json') } as never);
|
|
175
|
+
|
|
176
|
+
await expect(client.getModels()).rejects.toThrow(
|
|
177
|
+
/non-JSON response from http:\/\/repoburg\.test — the URL points to the wrong server/,
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repoburg backend runtime client — HTTP-only control surface.
|
|
3
|
+
*
|
|
4
|
+
* POST /sessions create a runtime session
|
|
5
|
+
* PATCH /sessions/:id update model / reasoning
|
|
6
|
+
* GET /sessions/:id fetch session (title + is_running)
|
|
7
|
+
* POST /sessions/:id/inputs start an agent turn
|
|
8
|
+
* GET /sessions/:id/inputs full snapshot (inputs + actions)
|
|
9
|
+
* GET /sessions/:id/inputs/:inputId/actions-with-logs
|
|
10
|
+
* POST /streaming/:sessionId/cancel abort the in-flight LLM call
|
|
11
|
+
* GET /llm-provider/models model catalog
|
|
12
|
+
*
|
|
13
|
+
* The MESSAGE STREAM lives on the separate agent-runtime SSE channel
|
|
14
|
+
* (agent-runtime-client.ts) — this client is control + snapshot only.
|
|
15
|
+
*
|
|
16
|
+
* Base URL: supplied by the caller — resolved per workspace via the
|
|
17
|
+
* platform daemon (backend-resolver.ts); REPOBURG_BACKEND_URL overrides.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Runtime API types (mirror the backend's DTOs/entities — read-only subset)
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
export interface RepoburgModel {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface RepoburgSessionDto {
|
|
30
|
+
session_title?: string;
|
|
31
|
+
model_id?: string;
|
|
32
|
+
reasoning_effort?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RepoburgSession {
|
|
36
|
+
id: string;
|
|
37
|
+
session_title?: string | null;
|
|
38
|
+
model_id?: string | null;
|
|
39
|
+
is_running?: boolean | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface RepoburgCreateInputDto {
|
|
43
|
+
user_prompt: string;
|
|
44
|
+
/** Per-input execution strategy. `auto_apply` keeps actions autonomous. */
|
|
45
|
+
execution_strategy: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface RepoburgExecutionLog {
|
|
49
|
+
output?: string | null;
|
|
50
|
+
error_message?: string | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface RepoburgAction {
|
|
54
|
+
id: string;
|
|
55
|
+
action_type: string;
|
|
56
|
+
file_path?: string | null;
|
|
57
|
+
command_string?: string | null;
|
|
58
|
+
status: string;
|
|
59
|
+
server_name?: string | null;
|
|
60
|
+
tool_name?: string | null;
|
|
61
|
+
arguments?: string | null;
|
|
62
|
+
plain?: string | null;
|
|
63
|
+
/** Raw selections JSON string (ask_user/final) — parsed by the client. */
|
|
64
|
+
selections?: string | null;
|
|
65
|
+
reason?: string | null;
|
|
66
|
+
order_of_execution?: number;
|
|
67
|
+
executionLogs?: RepoburgExecutionLog[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface RepoburgSessionInput {
|
|
71
|
+
id: string;
|
|
72
|
+
session_id: string;
|
|
73
|
+
role?: string;
|
|
74
|
+
user_prompt?: string | null;
|
|
75
|
+
raw_llm_response?: string | null;
|
|
76
|
+
thoughts?: string | null;
|
|
77
|
+
input_token_count?: number | null;
|
|
78
|
+
output_token_count?: number | null;
|
|
79
|
+
cached_token_count?: number | null;
|
|
80
|
+
error_message?: string | null;
|
|
81
|
+
aiActions?: RepoburgAction[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Interface — the provider depends on this, not on the HTTP implementation. */
|
|
85
|
+
export interface RepoburgRuntimeClient {
|
|
86
|
+
createSession(dto: RepoburgSessionDto): Promise<RepoburgSession>;
|
|
87
|
+
patchSession(id: string, dto: Partial<RepoburgSessionDto>): Promise<RepoburgSession>;
|
|
88
|
+
getSession(id: string): Promise<RepoburgSession>;
|
|
89
|
+
createInput(sessionId: string, dto: RepoburgCreateInputDto): Promise<RepoburgSessionInput>;
|
|
90
|
+
getInputWithActions(sessionId: string, inputId: string): Promise<RepoburgSessionInput>;
|
|
91
|
+
/** Full snapshot: all inputs of the session with actions + logs (ASC). */
|
|
92
|
+
getSessionInputs(sessionId: string): Promise<RepoburgSessionInput[]>;
|
|
93
|
+
/** Hard-delete every input AFTER the anchor (platform rewind parity). */
|
|
94
|
+
deleteInputsUntilHere(sessionId: string, anchorInputId: string): Promise<void>;
|
|
95
|
+
cancel(sessionId: string): Promise<{ success: boolean; cancelled: boolean }>;
|
|
96
|
+
getModels(): Promise<RepoburgModel[]>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// HTTP implementation
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
/** Default base URL of the repoburg backend. */
|
|
104
|
+
export function defaultRepoburgBaseUrl(): string {
|
|
105
|
+
return process.env.REPOBURG_BACKEND_URL || 'http://localhost:3000';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Detect an HTML response body (error page served by the wrong server). */
|
|
109
|
+
function isHtml(text: string): boolean {
|
|
110
|
+
return /^\s*<(?:!doctype|html)/i.test(text);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** HTTP client for the repoburg runtime control API (stateless fetch). */
|
|
114
|
+
export class RepoburgHttpClient implements RepoburgRuntimeClient {
|
|
115
|
+
private readonly baseUrl: string;
|
|
116
|
+
|
|
117
|
+
constructor(baseUrl: string = defaultRepoburgBaseUrl()) {
|
|
118
|
+
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Perform a JSON request. Throws a concise error on non-OK/non-JSON responses. */
|
|
122
|
+
private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
123
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
124
|
+
method,
|
|
125
|
+
headers: { 'Content-Type': 'application/json' },
|
|
126
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
127
|
+
});
|
|
128
|
+
const text = await response.text();
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
if (isHtml(text)) {
|
|
131
|
+
// An HTML error page means the URL does not serve the repoburg API.
|
|
132
|
+
throw new Error(
|
|
133
|
+
`${method} ${path} failed: ${response.status} HTML response from ${this.baseUrl} — the URL points to the wrong server`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
throw new Error(`${method} ${path} failed: ${response.status} ${text}`);
|
|
137
|
+
}
|
|
138
|
+
// Success with no payload — e.g. 204 No Content from the until-here delete.
|
|
139
|
+
if (text.length === 0) return undefined as unknown as T;
|
|
140
|
+
try {
|
|
141
|
+
return JSON.parse(text) as T;
|
|
142
|
+
} catch {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`${method} ${path} failed: non-JSON response from ${this.baseUrl} — the URL points to the wrong server`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async createSession(dto: RepoburgSessionDto): Promise<RepoburgSession> {
|
|
150
|
+
return this.request<RepoburgSession>('POST', '/sessions', dto);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async patchSession(id: string, dto: Partial<RepoburgSessionDto>): Promise<RepoburgSession> {
|
|
154
|
+
return this.request<RepoburgSession>('PATCH', `/sessions/${id}`, dto);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async getSession(id: string): Promise<RepoburgSession> {
|
|
158
|
+
return this.request<RepoburgSession>('GET', `/sessions/${id}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async createInput(sessionId: string, dto: RepoburgCreateInputDto): Promise<RepoburgSessionInput> {
|
|
162
|
+
return this.request<RepoburgSessionInput>('POST', `/sessions/${sessionId}/inputs`, dto);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async getInputWithActions(sessionId: string, inputId: string): Promise<RepoburgSessionInput> {
|
|
166
|
+
return this.request<RepoburgSessionInput>(
|
|
167
|
+
'GET',
|
|
168
|
+
`/sessions/${sessionId}/inputs/${inputId}/actions-with-logs`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async getSessionInputs(sessionId: string): Promise<RepoburgSessionInput[]> {
|
|
173
|
+
return this.request<RepoburgSessionInput[]>('GET', `/sessions/${sessionId}/inputs`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async deleteInputsUntilHere(sessionId: string, anchorInputId: string): Promise<void> {
|
|
177
|
+
await this.request<unknown>(
|
|
178
|
+
'DELETE',
|
|
179
|
+
`/sessions/${sessionId}/inputs/${anchorInputId}/until-here`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async cancel(sessionId: string): Promise<{ success: boolean; cancelled: boolean }> {
|
|
184
|
+
return this.request<{ success: boolean; cancelled: boolean }>(
|
|
185
|
+
'POST',
|
|
186
|
+
`/streaming/${sessionId}/cancel`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async getModels(): Promise<RepoburgModel[]> {
|
|
191
|
+
const result = await this.request<{ models: RepoburgModel[] }>('GET', '/llm-provider/models');
|
|
192
|
+
return result.models ?? [];
|
|
193
|
+
}
|
|
194
|
+
}
|