@oceanum/oceanumlab 4.6.0
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 +29 -0
- package/README.md +116 -0
- package/lib/DatameshUI.d.ts +5 -0
- package/lib/DatameshUI.js +8 -0
- package/lib/DatameshUI.js.map +1 -0
- package/lib/DatameshWidget.d.ts +59 -0
- package/lib/DatameshWidget.js +573 -0
- package/lib/DatameshWidget.js.map +1 -0
- package/lib/DatasourceItem.d.ts +12 -0
- package/lib/DatasourceItem.js +47 -0
- package/lib/DatasourceItem.js.map +1 -0
- package/lib/aiLoop.d.ts +60 -0
- package/lib/aiLoop.js +85 -0
- package/lib/aiLoop.js.map +1 -0
- package/lib/chatRouter.d.ts +64 -0
- package/lib/chatRouter.js +225 -0
- package/lib/chatRouter.js.map +1 -0
- package/lib/constants.d.ts +14 -0
- package/lib/constants.js +15 -0
- package/lib/constants.js.map +1 -0
- package/lib/conversationPin.d.ts +68 -0
- package/lib/conversationPin.js +120 -0
- package/lib/conversationPin.js.map +1 -0
- package/lib/handler.d.ts +8 -0
- package/lib/handler.js +36 -0
- package/lib/handler.js.map +1 -0
- package/lib/index.d.ts +16 -0
- package/lib/index.js +273 -0
- package/lib/index.js.map +1 -0
- package/lib/kernelHandoff.d.ts +61 -0
- package/lib/kernelHandoff.js +236 -0
- package/lib/kernelHandoff.js.map +1 -0
- package/lib/notebookContext.d.ts +48 -0
- package/lib/notebookContext.js +118 -0
- package/lib/notebookContext.js.map +1 -0
- package/lib/notebookHost.d.ts +6 -0
- package/lib/notebookHost.js +72 -0
- package/lib/notebookHost.js.map +1 -0
- package/lib/notebookRun.d.ts +37 -0
- package/lib/notebookRun.js +70 -0
- package/lib/notebookRun.js.map +1 -0
- package/lib/progress.d.ts +43 -0
- package/lib/progress.js +103 -0
- package/lib/progress.js.map +1 -0
- package/lib/sse.d.ts +26 -0
- package/lib/sse.js +112 -0
- package/lib/sse.js.map +1 -0
- package/package.json +151 -0
- package/schema/datamesh-connect.json +35 -0
- package/src/DatameshUI.tsx +15 -0
- package/src/DatameshWidget.tsx +909 -0
- package/src/DatasourceItem.tsx +124 -0
- package/src/__tests__/aiLoop.spec.ts +304 -0
- package/src/__tests__/chatRouter.spec.ts +315 -0
- package/src/__tests__/conversationPin.spec.ts +236 -0
- package/src/__tests__/kernelHandoff.spec.ts +106 -0
- package/src/__tests__/notebookContext.spec.ts +97 -0
- package/src/__tests__/notebookHost.spec.ts +195 -0
- package/src/__tests__/notebookRun.spec.ts +95 -0
- package/src/__tests__/notebookSnapshot.spec.ts +62 -0
- package/src/__tests__/oceanumlab.spec.ts +9 -0
- package/src/__tests__/sse.spec.ts +304 -0
- package/src/aiLoop.ts +140 -0
- package/src/chatRouter.ts +329 -0
- package/src/constants.ts +16 -0
- package/src/conversationPin.ts +148 -0
- package/src/global.d.ts +5 -0
- package/src/handler.ts +46 -0
- package/src/index.ts +327 -0
- package/src/kernelHandoff.ts +284 -0
- package/src/notebookContext.ts +144 -0
- package/src/notebookHost.ts +97 -0
- package/src/notebookRun.ts +104 -0
- package/src/progress.ts +121 -0
- package/src/sse.ts +127 -0
- package/src/svg.d.ts +4 -0
- package/style/base.css +5 -0
- package/style/icons/oceanum-dark.svg +1 -0
- package/style/icons/oceanum-light.svg +1 -0
- package/style/icons/oceanum.png +0 -0
- package/style/icons/oceanum.svg +1 -0
- package/style/index.css +477 -0
- package/style/index.js +1 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { caretDownIcon, caretRightIcon } from '@jupyterlab/ui-components';
|
|
3
|
+
import { ArrowBendDownRight } from '@phosphor-icons/react';
|
|
4
|
+
import '../style/index.css';
|
|
5
|
+
/**
|
|
6
|
+
* A React component for expandable containers.
|
|
7
|
+
*/
|
|
8
|
+
export const DatasourceItem = ({ datasource, insertDatasource, onMouseDown }) => {
|
|
9
|
+
const [expanded, setExpandedValue] = useState(false);
|
|
10
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
11
|
+
const handleToggleExpand = () => {
|
|
12
|
+
setExpandedValue(!expanded);
|
|
13
|
+
};
|
|
14
|
+
const startDrag = (e) => {
|
|
15
|
+
setIsDragging(true);
|
|
16
|
+
onMouseDown(e, datasource);
|
|
17
|
+
};
|
|
18
|
+
return (React.createElement("div", { className: expanded ? 'expanded' : '' },
|
|
19
|
+
React.createElement("div", { key: datasource.id, className: "datasource-item-title" },
|
|
20
|
+
React.createElement("button", { title: expanded ? 'Hide Details' : 'Show Details', onClick: handleToggleExpand, className: "datasource-item-expand" }, expanded ? (React.createElement(caretDownIcon.react, { tag: "span", elementPosition: "center", width: "20px" })) : (React.createElement(caretRightIcon.react, { tag: "span", elementPosition: "center", width: "20px" }))),
|
|
21
|
+
React.createElement("span", { title: datasource.description, className: isDragging
|
|
22
|
+
? 'datasource-item-name'
|
|
23
|
+
: 'datasource-item-name draggable', onClick: handleToggleExpand, onMouseDown: startDrag, onMouseUp: () => setIsDragging(false) }, datasource.description),
|
|
24
|
+
React.createElement("button", { title: "Insert code to load datasource", onClick: handleToggleExpand, className: "datasource-item-action" },
|
|
25
|
+
React.createElement(ArrowBendDownRight, { size: 18, onClick: e => insertDatasource(datasource) }))),
|
|
26
|
+
expanded && (React.createElement("div", { className: 'datasource-item-details' },
|
|
27
|
+
React.createElement("div", { className: "datasource-query-field" },
|
|
28
|
+
"Datasource id: ",
|
|
29
|
+
React.createElement("span", null, datasource.datasource)),
|
|
30
|
+
datasource.geofilter && (React.createElement("div", { className: "datasource-query-field" },
|
|
31
|
+
"Geofilter type:",
|
|
32
|
+
React.createElement("span", null, datasource.geofilter.type))),
|
|
33
|
+
datasource.variables && (React.createElement("div", { className: "datasource-query-field" },
|
|
34
|
+
"Variables:",
|
|
35
|
+
React.createElement("span", null, datasource.variables.join(',')))),
|
|
36
|
+
datasource.timefilter && (React.createElement("div", { className: "datasource-query-field" },
|
|
37
|
+
"Timefilter:",
|
|
38
|
+
React.createElement("span", null, datasource.timefilter.times[0]),
|
|
39
|
+
React.createElement("span", null, "to"),
|
|
40
|
+
React.createElement("span", null, datasource.timefilter.times[1]))),
|
|
41
|
+
datasource.spatialref && (React.createElement("div", { className: "datasource-query-field" },
|
|
42
|
+
"Spatial ref:",
|
|
43
|
+
React.createElement("span", null, datasource.spatialref))),
|
|
44
|
+
React.createElement("div", null,
|
|
45
|
+
React.createElement("a", { href: `https://oceanum.io/datasets/${datasource.datasource}`, target: "_blank" }, "Datasource details"))))));
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=DatasourceItem.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DatasourceItem.js","sourceRoot":"","sources":["../src/DatasourceItem.tsx"],"names":[],"mappings":"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"}
|
package/lib/aiLoop.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ChatMessage, OceanumResponse } from './chatRouter';
|
|
2
|
+
import type { ObservedRun } from './notebookRun';
|
|
3
|
+
/** What `inject` reports back: the chat text, and what ran if anything did. */
|
|
4
|
+
export interface PlacedResponse {
|
|
5
|
+
message: string;
|
|
6
|
+
runs: ObservedRun[];
|
|
7
|
+
/**
|
|
8
|
+
* A code cell could not be executed at all (no kernel, pending input, the
|
|
9
|
+
* notebook closed, Stop). Its run says so; observing it would only bring
|
|
10
|
+
* back more code that cannot run either.
|
|
11
|
+
*/
|
|
12
|
+
halted?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** The pieces the loop needs, injected so it can be tested without a notebook. */
|
|
15
|
+
export interface LoopDeps {
|
|
16
|
+
route(prompt: string, history: ChatMessage[], signal: AbortSignal): Promise<{
|
|
17
|
+
response: OceanumResponse;
|
|
18
|
+
hasCodeCellSelected: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
observe(prompt: string, history: ChatMessage[], runs: ObservedRun[], signal: AbortSignal): Promise<OceanumResponse>;
|
|
21
|
+
place(response: OceanumResponse, options: {
|
|
22
|
+
replaceCodeCell: boolean;
|
|
23
|
+
autoRun: boolean;
|
|
24
|
+
signal: AbortSignal;
|
|
25
|
+
}): Promise<PlacedResponse>;
|
|
26
|
+
}
|
|
27
|
+
export interface LoopOptions {
|
|
28
|
+
/** Run code cells as they are placed. Workflow 1; required for 2. */
|
|
29
|
+
autoRunCode: boolean;
|
|
30
|
+
/** Send each run's output back and place what comes next. Workflow 2. */
|
|
31
|
+
iterate: boolean;
|
|
32
|
+
/** Safety net on observe requests; see `runChatLoop`. */
|
|
33
|
+
maxRounds: number;
|
|
34
|
+
signal: AbortSignal;
|
|
35
|
+
}
|
|
36
|
+
export declare const STOPPED = "Stopped.";
|
|
37
|
+
/**
|
|
38
|
+
* One prompt, start to finish, under the two switches.
|
|
39
|
+
*
|
|
40
|
+
* The loop lives here rather than on the server because the CLIENT owns the
|
|
41
|
+
* kernel: only it can run a cell and read what came back. The server sees one
|
|
42
|
+
* turn at a time through `/api/chat/observe`.
|
|
43
|
+
*
|
|
44
|
+
* autoRunCode off -> place blocks, stop. (workflow 3)
|
|
45
|
+
* autoRunCode on, iterate off -> place, run, stop. (workflow 1)
|
|
46
|
+
* autoRunCode on, iterate on -> place, run, observe, repeat. (workflow 2)
|
|
47
|
+
*
|
|
48
|
+
* `iterate` without `autoRunCode` is inert: nothing ran, so there is nothing
|
|
49
|
+
* to observe. That falls out of the data (no runs) rather than a guard.
|
|
50
|
+
*
|
|
51
|
+
* Rounds end when nothing ran (the agent answered without code, so it is
|
|
52
|
+
* done), or on Stop. The server enforces its own EXECUTE_MAX_ROUNDS: at the
|
|
53
|
+
* cap it still answers, explaining the last run with any code stripped, and
|
|
54
|
+
* that code-free answer ends the loop here. `maxRounds` is therefore only a
|
|
55
|
+
* safety net against a server that keeps sending code, checked AFTER the
|
|
56
|
+
* observe so the server's explanation turn is always requested. Every round's
|
|
57
|
+
* message is kept: a chain that took three steps should read as three steps,
|
|
58
|
+
* not as the last one.
|
|
59
|
+
*/
|
|
60
|
+
export declare function runChatLoop(prompt: string, history: ChatMessage[], deps: LoopDeps, options: LoopOptions): Promise<string>;
|
package/lib/aiLoop.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export const STOPPED = 'Stopped.';
|
|
2
|
+
/**
|
|
3
|
+
* One prompt, start to finish, under the two switches.
|
|
4
|
+
*
|
|
5
|
+
* The loop lives here rather than on the server because the CLIENT owns the
|
|
6
|
+
* kernel: only it can run a cell and read what came back. The server sees one
|
|
7
|
+
* turn at a time through `/api/chat/observe`.
|
|
8
|
+
*
|
|
9
|
+
* autoRunCode off -> place blocks, stop. (workflow 3)
|
|
10
|
+
* autoRunCode on, iterate off -> place, run, stop. (workflow 1)
|
|
11
|
+
* autoRunCode on, iterate on -> place, run, observe, repeat. (workflow 2)
|
|
12
|
+
*
|
|
13
|
+
* `iterate` without `autoRunCode` is inert: nothing ran, so there is nothing
|
|
14
|
+
* to observe. That falls out of the data (no runs) rather than a guard.
|
|
15
|
+
*
|
|
16
|
+
* Rounds end when nothing ran (the agent answered without code, so it is
|
|
17
|
+
* done), or on Stop. The server enforces its own EXECUTE_MAX_ROUNDS: at the
|
|
18
|
+
* cap it still answers, explaining the last run with any code stripped, and
|
|
19
|
+
* that code-free answer ends the loop here. `maxRounds` is therefore only a
|
|
20
|
+
* safety net against a server that keeps sending code, checked AFTER the
|
|
21
|
+
* observe so the server's explanation turn is always requested. Every round's
|
|
22
|
+
* message is kept: a chain that took three steps should read as three steps,
|
|
23
|
+
* not as the last one.
|
|
24
|
+
*/
|
|
25
|
+
export async function runChatLoop(prompt, history, deps, options) {
|
|
26
|
+
const { signal } = options;
|
|
27
|
+
const messages = [];
|
|
28
|
+
const runs = [];
|
|
29
|
+
let response;
|
|
30
|
+
let replaceCodeCell;
|
|
31
|
+
try {
|
|
32
|
+
const first = await deps.route(prompt, history, signal);
|
|
33
|
+
response = first.response;
|
|
34
|
+
replaceCodeCell = first.hasCodeCellSelected;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if (signal.aborted) {
|
|
38
|
+
return STOPPED;
|
|
39
|
+
}
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
if (signal.aborted) {
|
|
43
|
+
return STOPPED;
|
|
44
|
+
}
|
|
45
|
+
for (let round = 1;; round++) {
|
|
46
|
+
const placed = await deps.place(response, {
|
|
47
|
+
replaceCodeCell,
|
|
48
|
+
autoRun: options.autoRunCode,
|
|
49
|
+
signal
|
|
50
|
+
});
|
|
51
|
+
messages.push(placed.message);
|
|
52
|
+
runs.push(...placed.runs);
|
|
53
|
+
replaceCodeCell = false;
|
|
54
|
+
if (signal.aborted) {
|
|
55
|
+
messages.push(STOPPED);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
if (!options.iterate || placed.runs.length === 0 || placed.halted) {
|
|
59
|
+
// Nothing ran this round, or nothing could, so there is nothing to
|
|
60
|
+
// observe.
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
if (round > options.maxRounds) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
response = await deps.observe(prompt, history, runs, signal);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
// Stop pressed while the request was in flight. What was already placed
|
|
71
|
+
// and explained stays in the chat; only the next step is dropped.
|
|
72
|
+
if (signal.aborted) {
|
|
73
|
+
messages.push(STOPPED);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
if (signal.aborted) {
|
|
79
|
+
messages.push(STOPPED);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return messages.filter(m => m.trim().length > 0).join('\n\n');
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=aiLoop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aiLoop.js","sourceRoot":"","sources":["../src/aiLoop.ts"],"names":[],"mappings":"AA+CA,MAAM,CAAC,MAAM,OAAO,GAAG,UAAU,CAAC;AAElC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAc,EACd,OAAsB,EACtB,IAAc,EACd,OAAoB;IAEpB,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC3B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAkB,EAAE,CAAC;IAE/B,IAAI,QAAyB,CAAC;IAC9B,IAAI,eAAwB,CAAC;IAC7B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACxD,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;QAC1B,eAAe,GAAG,KAAK,CAAC,mBAAmB,CAAC;IAC9C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,IAAI,KAAK,GAAG,CAAC,GAAI,KAAK,EAAE,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE;YACxC,eAAe;YACf,OAAO,EAAE,OAAO,CAAC,WAAW;YAC5B,MAAM;SACP,CAAC,CAAC;QACH,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QAC1B,eAAe,GAAG,KAAK,CAAC;QAExB,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,MAAM;QACR,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClE,mEAAmE;YACnE,WAAW;YACX,MAAM;QACR,CAAC;QACD,IAAI,KAAK,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;YAC9B,MAAM;QACR,CAAC;QAED,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,wEAAwE;YACxE,kEAAkE;YAClE,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACvB,MAAM;YACR,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,MAAM;QACR,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAChE,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ISettingRegistry } from '@jupyterlab/settingregistry';
|
|
2
|
+
import type { Progress } from './progress';
|
|
3
|
+
import type { ObservedRun } from './notebookRun';
|
|
4
|
+
import type { INotebookSnapshot } from './notebookContext';
|
|
5
|
+
/** One thing the backend asks us to place in the notebook. */
|
|
6
|
+
export interface Block {
|
|
7
|
+
type: 'code' | 'markdown';
|
|
8
|
+
content: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* What `/api/chat` answers with: one message, plus anything to place.
|
|
12
|
+
*
|
|
13
|
+
* This replaced a discriminated union of text/code/markdown responses
|
|
14
|
+
* (OCE-173). The union was exclusive, so the backend could not send a markdown
|
|
15
|
+
* table AND the query that produced it — it had to drop one. `message` is what
|
|
16
|
+
* goes in the chat window; `blocks` is what goes in the notebook, in order.
|
|
17
|
+
*
|
|
18
|
+
* `message` is always present now. On the old markdown variant it was optional,
|
|
19
|
+
* which is why the handoff used to fall back to 'Added markdown to notebook.'
|
|
20
|
+
*/
|
|
21
|
+
export interface OceanumResponse {
|
|
22
|
+
message: string;
|
|
23
|
+
blocks: Block[];
|
|
24
|
+
}
|
|
25
|
+
export interface ChatMessage {
|
|
26
|
+
role: 'user' | 'assistant';
|
|
27
|
+
content: string;
|
|
28
|
+
}
|
|
29
|
+
export interface RouteResult {
|
|
30
|
+
response: OceanumResponse;
|
|
31
|
+
hasCodeCellSelected: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare class ChatRouterError extends Error {
|
|
34
|
+
readonly statusCode?: number;
|
|
35
|
+
constructor(message: string, statusCode?: number);
|
|
36
|
+
}
|
|
37
|
+
export declare class ChatRouter {
|
|
38
|
+
private _settings;
|
|
39
|
+
private _notebook;
|
|
40
|
+
/**
|
|
41
|
+
* @param _notebook What the conversation's notebook holds -- the one its
|
|
42
|
+
* answers are placed in -- or null if there is nothing to send. Asked on
|
|
43
|
+
* every request, so it follows the conversation rather than whichever tab
|
|
44
|
+
* happens to be active.
|
|
45
|
+
*/
|
|
46
|
+
constructor(_settings: ISettingRegistry.ISettings, _notebook: () => Promise<INotebookSnapshot | null>);
|
|
47
|
+
route(prompt: string, chatHistory?: ChatMessage[], signal?: AbortSignal, onProgress?: (progress: Progress) => void): Promise<RouteResult>;
|
|
48
|
+
/**
|
|
49
|
+
* The notebook half of the execute loop: the code ran in the user's own
|
|
50
|
+
* kernel, here is what happened, what next? Same context as `route`, plus
|
|
51
|
+
* every run so far, so the agent sees the whole chain it is continuing.
|
|
52
|
+
*/
|
|
53
|
+
observe(prompt: string, chatHistory: ChatMessage[], runs: ObservedRun[], signal?: AbortSignal, onProgress?: (progress: Progress) => void): Promise<OceanumResponse>;
|
|
54
|
+
private _gather;
|
|
55
|
+
private _send;
|
|
56
|
+
/**
|
|
57
|
+
* Read the streamed form, reporting progress, and return the final answer.
|
|
58
|
+
*
|
|
59
|
+
* The `done` event is authoritative: the events before it are for the user
|
|
60
|
+
* to look at, not for the client to reassemble. Anything else and a client
|
|
61
|
+
* that missed a frame would silently answer with less than the agent said.
|
|
62
|
+
*/
|
|
63
|
+
private _drain;
|
|
64
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { OCEANUM_AI_BACKEND_URL } from './constants';
|
|
2
|
+
import { readSse } from './sse';
|
|
3
|
+
import { formatNotebookCells } from './notebookContext';
|
|
4
|
+
export class ChatRouterError extends Error {
|
|
5
|
+
constructor(message, statusCode) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.statusCode = statusCode;
|
|
8
|
+
this.name = 'ChatRouterError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Check the body is the shape we expect before anything reads it.
|
|
13
|
+
*
|
|
14
|
+
* A bare `as OceanumResponse` cast used to be enough because the shape had not
|
|
15
|
+
* changed since the extension was written. It has now (OCE-173), and an
|
|
16
|
+
* extension pinned to one contract will meet a backend serving the other during
|
|
17
|
+
* any deploy where the two are not released together: the old body has no
|
|
18
|
+
* `blocks`, so `response.blocks.length` throws deep inside the handoff and the
|
|
19
|
+
* user sees nothing happen. One check turns that into the error the chat window
|
|
20
|
+
* already knows how to show.
|
|
21
|
+
*/
|
|
22
|
+
function asOceanumResponse(data) {
|
|
23
|
+
const body = data;
|
|
24
|
+
if (!body ||
|
|
25
|
+
typeof body.message !== 'string' ||
|
|
26
|
+
!Array.isArray(body.blocks)) {
|
|
27
|
+
throw new ChatRouterError('Unexpected response from the AI backend. It may be running an ' +
|
|
28
|
+
'incompatible version of the chat API.');
|
|
29
|
+
}
|
|
30
|
+
return { message: body.message, blocks: body.blocks };
|
|
31
|
+
}
|
|
32
|
+
export class ChatRouter {
|
|
33
|
+
/**
|
|
34
|
+
* @param _notebook What the conversation's notebook holds -- the one its
|
|
35
|
+
* answers are placed in -- or null if there is nothing to send. Asked on
|
|
36
|
+
* every request, so it follows the conversation rather than whichever tab
|
|
37
|
+
* happens to be active.
|
|
38
|
+
*/
|
|
39
|
+
constructor(_settings, _notebook) {
|
|
40
|
+
this._settings = _settings;
|
|
41
|
+
this._notebook = _notebook;
|
|
42
|
+
}
|
|
43
|
+
async route(prompt, chatHistory = [], signal, onProgress) {
|
|
44
|
+
const { payload, isCodeCell, context } = await this._gather(prompt, chatHistory);
|
|
45
|
+
const data = await this._send('/api/chat', payload, signal, onProgress);
|
|
46
|
+
return {
|
|
47
|
+
response: asOceanumResponse(data),
|
|
48
|
+
hasCodeCellSelected: isCodeCell && context.trim().length > 0
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The notebook half of the execute loop: the code ran in the user's own
|
|
53
|
+
* kernel, here is what happened, what next? Same context as `route`, plus
|
|
54
|
+
* every run so far, so the agent sees the whole chain it is continuing.
|
|
55
|
+
*/
|
|
56
|
+
async observe(prompt, chatHistory, runs, signal, onProgress) {
|
|
57
|
+
const { payload } = await this._gather(prompt, chatHistory);
|
|
58
|
+
const data = await this._send('/api/chat/observe', { ...payload, runs }, signal, onProgress);
|
|
59
|
+
return asOceanumResponse(data);
|
|
60
|
+
}
|
|
61
|
+
async _gather(prompt, chatHistory) {
|
|
62
|
+
let context = '';
|
|
63
|
+
let isCodeCell = false;
|
|
64
|
+
let notebookCells = [];
|
|
65
|
+
try {
|
|
66
|
+
const snapshot = await this._notebook();
|
|
67
|
+
if (snapshot) {
|
|
68
|
+
notebookCells = formatNotebookCells(snapshot.cells);
|
|
69
|
+
// Answers are placed in this same notebook, so its selected cell is
|
|
70
|
+
// the one a code answer may replace.
|
|
71
|
+
if (snapshot.selected) {
|
|
72
|
+
context = snapshot.selected.source;
|
|
73
|
+
isCodeCell = snapshot.selected.isCode;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (_a) {
|
|
78
|
+
// context is optional — never throw
|
|
79
|
+
}
|
|
80
|
+
// Build payload with all context
|
|
81
|
+
const payload = { prompt };
|
|
82
|
+
if (context) {
|
|
83
|
+
if (isCodeCell) {
|
|
84
|
+
payload.codeContext = context;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
payload.context = context;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Include chat history (excluding the current prompt which is already in 'prompt')
|
|
91
|
+
if (chatHistory.length > 0) {
|
|
92
|
+
payload.chatHistory = chatHistory;
|
|
93
|
+
}
|
|
94
|
+
// Include the conversation's notebook cells
|
|
95
|
+
if (notebookCells.length > 0) {
|
|
96
|
+
payload.notebookCells = notebookCells;
|
|
97
|
+
}
|
|
98
|
+
return { payload, isCodeCell, context };
|
|
99
|
+
}
|
|
100
|
+
async _send(path, payload, signal, onProgress) {
|
|
101
|
+
var _a;
|
|
102
|
+
const token = this._settings.get('datameshToken').composite;
|
|
103
|
+
if (!token) {
|
|
104
|
+
throw new ChatRouterError('Datamesh token not configured. Set your token in Settings → Oceanum.io.');
|
|
105
|
+
}
|
|
106
|
+
let response;
|
|
107
|
+
try {
|
|
108
|
+
response = await fetch(`${OCEANUM_AI_BACKEND_URL}${path}`, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: {
|
|
111
|
+
'Content-Type': 'application/json',
|
|
112
|
+
'X-Datamesh-Token': token,
|
|
113
|
+
// Ask for the streamed form only when someone is listening. The
|
|
114
|
+
// server requires the media type to be NAMED -- a wildcard never
|
|
115
|
+
// selects it -- so omitting this is what keeps the plain JSON path
|
|
116
|
+
// byte-identical to what it always was.
|
|
117
|
+
...(onProgress ? { Accept: 'text/event-stream' } : {})
|
|
118
|
+
},
|
|
119
|
+
body: JSON.stringify(payload),
|
|
120
|
+
signal
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
// The user pressed Stop. Not a failure, and not "could not reach"; the
|
|
125
|
+
// caller checks `signal.aborted` and stays quiet.
|
|
126
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
127
|
+
throw err;
|
|
128
|
+
}
|
|
129
|
+
throw new ChatRouterError(`Could not reach Oceanum AI backend at ${OCEANUM_AI_BACKEND_URL}. Is it running?`);
|
|
130
|
+
}
|
|
131
|
+
if (response.status === 401) {
|
|
132
|
+
throw new ChatRouterError('Invalid or expired Datamesh token.', 401);
|
|
133
|
+
}
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
let detail = '';
|
|
136
|
+
try {
|
|
137
|
+
const body = await response.json();
|
|
138
|
+
detail = (_a = body.detail) !== null && _a !== void 0 ? _a : JSON.stringify(body);
|
|
139
|
+
}
|
|
140
|
+
catch (_b) {
|
|
141
|
+
detail = response.statusText;
|
|
142
|
+
}
|
|
143
|
+
throw new ChatRouterError(`Backend error: ${detail}`, response.status);
|
|
144
|
+
}
|
|
145
|
+
// Stop can also land while the body is still being read; that rejects
|
|
146
|
+
// here, not in the fetch above, and the caller treats it the same way.
|
|
147
|
+
if (onProgress && isEventStream(response)) {
|
|
148
|
+
return this._drain(response, onProgress, signal);
|
|
149
|
+
}
|
|
150
|
+
return response.json();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Read the streamed form, reporting progress, and return the final answer.
|
|
154
|
+
*
|
|
155
|
+
* The `done` event is authoritative: the events before it are for the user
|
|
156
|
+
* to look at, not for the client to reassemble. Anything else and a client
|
|
157
|
+
* that missed a frame would silently answer with less than the agent said.
|
|
158
|
+
*/
|
|
159
|
+
async _drain(response, onProgress, signal) {
|
|
160
|
+
var _a;
|
|
161
|
+
if (!response.body) {
|
|
162
|
+
// A proxy buffered the whole response, or this runtime has no
|
|
163
|
+
// ReadableStream. Either way there is nothing to stream; the body is
|
|
164
|
+
// still the answer, so fall back rather than fail.
|
|
165
|
+
return response.json();
|
|
166
|
+
}
|
|
167
|
+
let answer;
|
|
168
|
+
try {
|
|
169
|
+
for await (const event of readSse(response.body, signal)) {
|
|
170
|
+
if (event.event === 'status') {
|
|
171
|
+
const data = parseJson(event.data);
|
|
172
|
+
if (data === null || data === void 0 ? void 0 : data.phase) {
|
|
173
|
+
onProgress(data);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else if (event.event === 'done') {
|
|
177
|
+
answer = parseJson(event.data);
|
|
178
|
+
}
|
|
179
|
+
else if (event.event === 'error') {
|
|
180
|
+
const data = parseJson(event.data);
|
|
181
|
+
// The status was 200 before this could happen, so the failure travels
|
|
182
|
+
// in the frame and there was nothing for the check above to throw on.
|
|
183
|
+
throw new ChatRouterError(`Backend error: ${(_a = data === null || data === void 0 ? void 0 : data.detail) !== null && _a !== void 0 ? _a : 'the request failed'}`, data === null || data === void 0 ? void 0 : data.status_code);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
// Stop fails the read with an AbortError, which the caller reports as
|
|
189
|
+
// Stop, and an `error` event is already the chat's error. Anything else
|
|
190
|
+
// is the connection going away mid-answer -- a redeploy, the network --
|
|
191
|
+
// which fetch reports as a bare TypeError ("terminated"). Left as it
|
|
192
|
+
// was, it reached the command as unexpected and the chat showed nothing.
|
|
193
|
+
if ((signal === null || signal === void 0 ? void 0 : signal.aborted) || err instanceof ChatRouterError) {
|
|
194
|
+
throw err;
|
|
195
|
+
}
|
|
196
|
+
throw new ChatRouterError('The connection to Oceanum AI was lost before the response was complete.');
|
|
197
|
+
}
|
|
198
|
+
if (answer === undefined) {
|
|
199
|
+
// The stream ended without its terminal event -- a dropped connection,
|
|
200
|
+
// or a proxy closing a response that looked idle. Treated as a failure
|
|
201
|
+
// rather than a partial answer, which is indistinguishable from a
|
|
202
|
+
// complete one once it reaches the chat.
|
|
203
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
204
|
+
throw new DOMException('Aborted', 'AbortError');
|
|
205
|
+
}
|
|
206
|
+
throw new ChatRouterError('The response ended before it was complete.');
|
|
207
|
+
}
|
|
208
|
+
return answer;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function isEventStream(response) {
|
|
212
|
+
var _a, _b;
|
|
213
|
+
return ((_b = (_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('text/event-stream')) !== null && _b !== void 0 ? _b : false);
|
|
214
|
+
}
|
|
215
|
+
/** Parse one event's data, returning null rather than throwing: a malformed
|
|
216
|
+
* frame should cost a progress update, not the answer. */
|
|
217
|
+
function parseJson(data) {
|
|
218
|
+
try {
|
|
219
|
+
return JSON.parse(data);
|
|
220
|
+
}
|
|
221
|
+
catch (_a) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=chatRouter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chatRouter.js","sourceRoot":"","sources":["../src/chatRouter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-time constants for Oceanum AI integration.
|
|
3
|
+
* These values are compiled into the extension.
|
|
4
|
+
*/
|
|
5
|
+
/** URL of the Oceanum AI backend service */
|
|
6
|
+
export declare const OCEANUM_AI_BACKEND_URL = "https://ai.oceanum.io";
|
|
7
|
+
/**
|
|
8
|
+
* Safety net on observe requests per prompt. The server's EXECUTE_MAX_ROUNDS
|
|
9
|
+
* is what actually caps the chain (at its cap it answers without code, which
|
|
10
|
+
* ends the loop); this only stops a client talking to a server that keeps
|
|
11
|
+
* sending code, and is checked after each observe so the server's explanation
|
|
12
|
+
* turn is always requested.
|
|
13
|
+
*/
|
|
14
|
+
export declare const MAX_OBSERVE_ROUNDS = 8;
|
package/lib/constants.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-time constants for Oceanum AI integration.
|
|
3
|
+
* These values are compiled into the extension.
|
|
4
|
+
*/
|
|
5
|
+
/** URL of the Oceanum AI backend service */
|
|
6
|
+
export const OCEANUM_AI_BACKEND_URL = 'https://ai.oceanum.io';
|
|
7
|
+
/**
|
|
8
|
+
* Safety net on observe requests per prompt. The server's EXECUTE_MAX_ROUNDS
|
|
9
|
+
* is what actually caps the chain (at its cap it answers without code, which
|
|
10
|
+
* ends the loop); this only stops a client talking to a server that keeps
|
|
11
|
+
* sending code, and is checked after each observe so the server's explanation
|
|
12
|
+
* turn is always requested.
|
|
13
|
+
*/
|
|
14
|
+
export const MAX_OBSERVE_ROUNDS = 8;
|
|
15
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,4CAA4C;AAC5C,MAAM,CAAC,MAAM,sBAAsB,GAAG,uBAAuB,CAAC;AAE9D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { NotebookPanel } from '@jupyterlab/notebook';
|
|
2
|
+
/**
|
|
3
|
+
* What ConversationPin needs from JupyterLab -- passed in, so the pin can be
|
|
4
|
+
* tested without it. Both `create` and `reopen` must resolve only once the
|
|
5
|
+
* notebook's contents have loaded: loading replaces every cell, so anything
|
|
6
|
+
* placed before then is wiped.
|
|
7
|
+
*/
|
|
8
|
+
export interface INotebookHost {
|
|
9
|
+
/** The notebook in the active main-area tab, or null if that is not one. */
|
|
10
|
+
activeTab(): NotebookPanel | null;
|
|
11
|
+
/** A new notebook, made the active tab. Null if none can be created. */
|
|
12
|
+
create(): Promise<NotebookPanel | null>;
|
|
13
|
+
/** The notebook at `path`, opened again as a tab. Null if it cannot be. */
|
|
14
|
+
reopen(path: string): Promise<NotebookPanel | null>;
|
|
15
|
+
/** Bring `panel`'s tab to the front. */
|
|
16
|
+
activate(panel: NotebookPanel): void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The notebook the current conversation lives in: what the agent is shown,
|
|
20
|
+
* and where its answers go.
|
|
21
|
+
*
|
|
22
|
+
* Pinned when the conversation starts -- New chat, or the first message after
|
|
23
|
+
* the panel opens -- to the notebook in the ACTIVE TAB or, when that tab is
|
|
24
|
+
* not a notebook, to a new notebook made the active tab. Kept for the whole
|
|
25
|
+
* conversation: switching tabs does not move it, a rename is followed, and a
|
|
26
|
+
* pinned notebook that has been closed is opened again when an answer has
|
|
27
|
+
* cells to place in it. The active tab rather than the tracker's current
|
|
28
|
+
* notebook, which is the LAST-FOCUSED notebook even with a terminal or a text
|
|
29
|
+
* file in front.
|
|
30
|
+
*/
|
|
31
|
+
export declare class ConversationPin {
|
|
32
|
+
private readonly _host;
|
|
33
|
+
private _panel;
|
|
34
|
+
private _path;
|
|
35
|
+
private _started;
|
|
36
|
+
private _queue;
|
|
37
|
+
constructor(_host: INotebookHost);
|
|
38
|
+
/** Start a new conversation. Resolves to its notebook's name. */
|
|
39
|
+
start(): Promise<string | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Start the conversation if nothing has, and report its notebook's name
|
|
42
|
+
* either way.
|
|
43
|
+
*/
|
|
44
|
+
ensure(): Promise<string | null>;
|
|
45
|
+
/**
|
|
46
|
+
* The conversation's notebook if it is open, starting the conversation if
|
|
47
|
+
* nothing has -- but NOT opening a closed one again: that is for placing an
|
|
48
|
+
* answer, not for asking a question. See path() for reading a closed one.
|
|
49
|
+
*/
|
|
50
|
+
current(): Promise<NotebookPanel | null>;
|
|
51
|
+
/**
|
|
52
|
+
* The conversation's notebook, opened again if it has been closed. When it
|
|
53
|
+
* cannot be -- deleted, or gone some other way -- a new notebook takes its
|
|
54
|
+
* place, so the conversation always has one. Null only if none can be made.
|
|
55
|
+
*/
|
|
56
|
+
notebook(): Promise<NotebookPanel | null>;
|
|
57
|
+
/** The conversation's notebook, brought to the front: see notebook(). */
|
|
58
|
+
show(): Promise<NotebookPanel | null>;
|
|
59
|
+
/** Where the notebook's file is -- its last known place while it is closed. */
|
|
60
|
+
path(): string | null;
|
|
61
|
+
/** The notebook's name -- its last known one while it is closed. */
|
|
62
|
+
name(): string | null;
|
|
63
|
+
private _live;
|
|
64
|
+
private _startNow;
|
|
65
|
+
private _pin;
|
|
66
|
+
private _onPathChanged;
|
|
67
|
+
private _serial;
|
|
68
|
+
}
|