@shivaedev/agent-opencode 0.1.0 → 0.3.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/CHANGELOG.md +12 -0
- package/README.md +58 -1
- package/dist/Provider.d.ts +16 -0
- package/dist/Provider.d.ts.map +1 -0
- package/dist/Provider.js +4 -0
- package/dist/Provider.js.map +1 -0
- package/dist/adapters/MessageUpdatedProperties.d.ts +10 -0
- package/dist/adapters/MessageUpdatedProperties.d.ts.map +1 -0
- package/dist/adapters/MessageUpdatedProperties.js +10 -0
- package/dist/adapters/MessageUpdatedProperties.js.map +1 -0
- package/dist/adapters/OpencodeRequest.d.ts +6 -0
- package/dist/adapters/OpencodeRequest.d.ts.map +1 -0
- package/dist/adapters/OpencodeRequest.js +2 -0
- package/dist/adapters/OpencodeRequest.js.map +1 -0
- package/dist/adapters/answerToolRequest.d.ts +4 -0
- package/dist/adapters/answerToolRequest.d.ts.map +1 -0
- package/dist/adapters/answerToolRequest.js +106 -0
- package/dist/adapters/answerToolRequest.js.map +1 -0
- package/dist/adapters/listeningUrl.d.ts +2 -0
- package/dist/adapters/listeningUrl.d.ts.map +1 -0
- package/dist/adapters/listeningUrl.js +5 -0
- package/dist/adapters/listeningUrl.js.map +1 -0
- package/dist/adapters/makeOpencodeServer.d.ts +7 -0
- package/dist/adapters/makeOpencodeServer.d.ts.map +1 -0
- package/dist/adapters/makeOpencodeServer.js +49 -0
- package/dist/adapters/makeOpencodeServer.js.map +1 -0
- package/dist/adapters/mapping.d.ts +7 -0
- package/dist/adapters/mapping.d.ts.map +1 -0
- package/dist/adapters/mapping.js +11 -0
- package/dist/adapters/mapping.js.map +1 -0
- package/dist/adapters/messageAuthors.d.ts +11 -0
- package/dist/adapters/messageAuthors.d.ts.map +1 -0
- package/dist/adapters/messageAuthors.js +24 -0
- package/dist/adapters/messageAuthors.js.map +1 -0
- package/dist/adapters/openSseBuffer.d.ts +6 -0
- package/dist/adapters/openSseBuffer.d.ts.map +1 -0
- package/dist/adapters/openSseBuffer.js +34 -0
- package/dist/adapters/openSseBuffer.js.map +1 -0
- package/dist/adapters/openTurnBoundary.d.ts +11 -0
- package/dist/adapters/openTurnBoundary.d.ts.map +1 -0
- package/dist/adapters/openTurnBoundary.js +46 -0
- package/dist/adapters/openTurnBoundary.js.map +1 -0
- package/dist/adapters/opencodePlugin.config.d.ts +11 -0
- package/dist/adapters/opencodePlugin.config.d.ts.map +1 -0
- package/dist/adapters/opencodePlugin.config.js +9 -0
- package/dist/adapters/opencodePlugin.config.js.map +1 -0
- package/dist/adapters/partEvents.d.ts +7 -0
- package/dist/adapters/partEvents.d.ts.map +1 -0
- package/dist/adapters/partEvents.js +62 -0
- package/dist/adapters/partEvents.js.map +1 -0
- package/dist/adapters/projection.d.ts +7 -0
- package/dist/adapters/projection.d.ts.map +1 -0
- package/dist/adapters/projection.js +41 -0
- package/dist/adapters/projection.js.map +1 -0
- package/dist/adapters/protocol.d.ts +36 -0
- package/dist/adapters/protocol.d.ts.map +1 -0
- package/dist/adapters/protocol.js +32 -0
- package/dist/adapters/protocol.js.map +1 -0
- package/dist/adapters/protocolParts.d.ts +48 -0
- package/dist/adapters/protocolParts.d.ts.map +1 -0
- package/dist/adapters/protocolParts.js +47 -0
- package/dist/adapters/protocolParts.js.map +1 -0
- package/dist/adapters/providerAddress.d.ts +5 -0
- package/dist/adapters/providerAddress.d.ts.map +1 -0
- package/dist/adapters/providerAddress.js +21 -0
- package/dist/adapters/providerAddress.js.map +1 -0
- package/dist/adapters/serveOpencode.d.ts +13 -0
- package/dist/adapters/serveOpencode.d.ts.map +1 -0
- package/dist/adapters/serveOpencode.js +75 -0
- package/dist/adapters/serveOpencode.js.map +1 -0
- package/dist/adapters/serveToolRequests.d.ts +4 -0
- package/dist/adapters/serveToolRequests.d.ts.map +1 -0
- package/dist/adapters/serveToolRequests.js +23 -0
- package/dist/adapters/serveToolRequests.js.map +1 -0
- package/dist/adapters/servedTools.d.ts +4 -0
- package/dist/adapters/servedTools.d.ts.map +1 -0
- package/dist/adapters/servedTools.js +9 -0
- package/dist/adapters/servedTools.js.map +1 -0
- package/dist/adapters/sessionFrames.d.ts +7 -0
- package/dist/adapters/sessionFrames.d.ts.map +1 -0
- package/dist/adapters/sessionFrames.js +16 -0
- package/dist/adapters/sessionFrames.js.map +1 -0
- package/dist/adapters/toolEvents.d.ts +10 -0
- package/dist/adapters/toolEvents.d.ts.map +1 -0
- package/dist/adapters/toolEvents.js +30 -0
- package/dist/adapters/toolEvents.js.map +1 -0
- package/dist/adapters/toolNames.d.ts +6 -0
- package/dist/adapters/toolNames.d.ts.map +1 -0
- package/dist/adapters/toolNames.js +8 -0
- package/dist/adapters/toolNames.js.map +1 -0
- package/dist/adapters/toolSessions.d.ts +11 -0
- package/dist/adapters/toolSessions.d.ts.map +1 -0
- package/dist/adapters/toolSessions.js +15 -0
- package/dist/adapters/toolSessions.js.map +1 -0
- package/dist/callWhileOpen.d.ts +4 -0
- package/dist/callWhileOpen.d.ts.map +1 -0
- package/dist/callWhileOpen.js +9 -0
- package/dist/callWhileOpen.js.map +1 -0
- package/dist/failure.d.ts +4 -0
- package/dist/failure.d.ts.map +1 -0
- package/dist/failure.js +8 -0
- package/dist/failure.js.map +1 -0
- package/dist/listOpencodeModels.d.ts +11 -0
- package/dist/listOpencodeModels.d.ts.map +1 -0
- package/dist/listOpencodeModels.js +31 -0
- package/dist/listOpencodeModels.js.map +1 -0
- package/dist/openOpencodeSession.d.ts +5 -0
- package/dist/openOpencodeSession.d.ts.map +1 -0
- package/dist/openOpencodeSession.js +40 -0
- package/dist/openOpencodeSession.js.map +1 -0
- package/dist/opencode.d.ts +9 -1
- package/dist/opencode.d.ts.map +1 -1
- package/dist/opencode.js +49 -1
- package/dist/opencode.js.map +1 -1
- package/dist/operations.d.ts +367 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +87 -0
- package/dist/operations.js.map +1 -0
- package/dist/options.d.ts +30 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +2 -0
- package/dist/options.js.map +1 -0
- package/dist/sessionOpen.d.ts +19 -0
- package/dist/sessionOpen.d.ts.map +1 -0
- package/dist/sessionOpen.js +60 -0
- package/dist/sessionOpen.js.map +1 -0
- package/dist/tools.d.ts +14 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +2 -0
- package/dist/tools.js.map +1 -0
- package/dist/turnRequests.d.ts +13 -0
- package/dist/turnRequests.d.ts.map +1 -0
- package/dist/turnRequests.js +19 -0
- package/dist/turnRequests.js.map +1 -0
- package/dist/turnState.d.ts +24 -0
- package/dist/turnState.d.ts.map +1 -0
- package/dist/turnState.js +27 -0
- package/dist/turnState.js.map +1 -0
- package/dist/turns.d.ts +19 -0
- package/dist/turns.d.ts.map +1 -0
- package/dist/turns.js +57 -0
- package/dist/turns.js.map +1 -0
- package/package.json +35 -6
- package/src/Provider.ts +14 -0
- package/src/adapters/MessageUpdatedProperties.ts +9 -0
- package/src/adapters/OpencodeRequest.ts +5 -0
- package/src/adapters/answerToolRequest.ts +121 -0
- package/src/adapters/listeningUrl.ts +4 -0
- package/src/adapters/makeOpencodeServer.ts +77 -0
- package/src/adapters/mapping.ts +14 -0
- package/src/adapters/messageAuthors.ts +31 -0
- package/src/adapters/openSseBuffer.ts +36 -0
- package/src/adapters/openTurnBoundary.ts +58 -0
- package/src/adapters/opencodePlugin.config.ts +17 -0
- package/src/adapters/partEvents.ts +85 -0
- package/src/adapters/projection.ts +46 -0
- package/src/adapters/protocol.ts +31 -0
- package/src/adapters/protocolParts.ts +48 -0
- package/src/adapters/providerAddress.ts +22 -0
- package/src/adapters/serveOpencode.ts +93 -0
- package/src/adapters/serveToolRequests.ts +24 -0
- package/src/adapters/servedTools.ts +11 -0
- package/src/adapters/sessionFrames.ts +25 -0
- package/src/adapters/toolEvents.ts +42 -0
- package/src/adapters/toolNames.ts +7 -0
- package/src/adapters/toolSessions.ts +22 -0
- package/src/callWhileOpen.ts +16 -0
- package/src/failure.ts +7 -0
- package/src/listOpencodeModels.ts +34 -0
- package/src/openOpencodeSession.ts +60 -0
- package/src/opencode.ts +61 -1
- package/src/operations.ts +111 -0
- package/src/options.ts +29 -0
- package/src/sessionOpen.ts +74 -0
- package/src/tools.ts +13 -0
- package/src/turnRequests.ts +25 -0
- package/src/turnState.ts +41 -0
- package/src/turns.ts +89 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { Provider } from "#Provider.ts";
|
|
3
|
+
export const turnRequests = Effect.fnUntraced(function* (sessionId, cwd, settings) {
|
|
4
|
+
const server = yield* Provider;
|
|
5
|
+
const query = { directory: cwd };
|
|
6
|
+
return {
|
|
7
|
+
abort: server.post({
|
|
8
|
+
body: {},
|
|
9
|
+
path: `/session/${sessionId}/abort`,
|
|
10
|
+
query,
|
|
11
|
+
}),
|
|
12
|
+
prompt: (text) => server.post({
|
|
13
|
+
body: { ...settings, parts: [{ text, type: "text" }] },
|
|
14
|
+
path: `/session/${sessionId}/prompt_async`,
|
|
15
|
+
query,
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
//# sourceMappingURL=turnRequests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turnRequests.js","sourceRoot":"","sources":["../src/turnRequests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAMxC,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,SAAiB,EAAE,GAAW,EAAE,QAAwB;IAChH,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC;IAC/B,MAAM,KAAK,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;IACjC,OAAO;QACN,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC;YAClB,IAAI,EAAE,EAAE;YACR,IAAI,EAAE,YAAY,SAAS,QAAQ;YACnC,KAAK;SACL,CAAC;QACF,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE,CACxB,MAAM,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,EAAE,GAAG,QAAQ,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE;YACtD,IAAI,EAAE,YAAY,SAAS,eAAe;YAC1C,KAAK;SACL,CAAC;KACH,CAAC;AACH,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Deferred } from "effect";
|
|
2
|
+
import type { BackendFailure } from "@shivaedev/agent/failures.ts";
|
|
3
|
+
export interface PendingText {
|
|
4
|
+
readonly accepted: Deferred.Deferred<void, BackendFailure>;
|
|
5
|
+
readonly text: string;
|
|
6
|
+
}
|
|
7
|
+
export interface OpenTurnState {
|
|
8
|
+
readonly "_tag": "open";
|
|
9
|
+
readonly pending: readonly PendingText[];
|
|
10
|
+
readonly running: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface ClosedTurnState {
|
|
13
|
+
readonly "_tag": "closed";
|
|
14
|
+
}
|
|
15
|
+
export type TurnState = ClosedTurnState | OpenTurnState;
|
|
16
|
+
export declare const closed: ClosedTurnState;
|
|
17
|
+
export declare const idle: OpenTurnState;
|
|
18
|
+
export declare const SESSION_CLOSED: import("@shivaedev/agent/failures.ts").ProtocolFailure;
|
|
19
|
+
export declare function withPending(state: OpenTurnState, text: PendingText): OpenTurnState;
|
|
20
|
+
export declare function running(state: OpenTurnState): OpenTurnState;
|
|
21
|
+
export declare function rested(state: OpenTurnState): OpenTurnState;
|
|
22
|
+
export declare function sent(state: TurnState): TurnState;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=turnState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turnState.d.ts","sourceRoot":"","sources":["../src/turnState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IAC3D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AACD,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC1B;AACD,UAAU,eAAe;IACxB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;CAC1B;AACD,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG,aAAa,CAAC;AACxD,eAAO,MAAM,MAAM,EAAE,eAAsC,CAAC;AAC5D,eAAO,MAAM,IAAI,EAAE,aAIlB,CAAC;AACF,eAAO,MAAM,cAAc,wDAAyE,CAAC;AACrG,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,GAAG,aAAa,CAElF;AACD,wBAAgB,OAAO,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CAK3D;AACD,wBAAgB,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CAK1D;AACD,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAEhD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { opencodeFailure } from "#failure.ts";
|
|
2
|
+
export const closed = { "_tag": "closed" };
|
|
3
|
+
export const idle = {
|
|
4
|
+
"_tag": "open",
|
|
5
|
+
pending: [],
|
|
6
|
+
running: false,
|
|
7
|
+
};
|
|
8
|
+
export const SESSION_CLOSED = opencodeFailure("session closed before delivery reached the provider");
|
|
9
|
+
export function withPending(state, text) {
|
|
10
|
+
return { ...state, pending: [...state.pending, text] };
|
|
11
|
+
}
|
|
12
|
+
export function running(state) {
|
|
13
|
+
return {
|
|
14
|
+
...state,
|
|
15
|
+
running: true,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function rested(state) {
|
|
19
|
+
return {
|
|
20
|
+
...state,
|
|
21
|
+
running: false,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function sent(state) {
|
|
25
|
+
return state._tag === "closed" ? state : { ...state, pending: state.pending.slice(1) };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=turnState.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turnState.js","sourceRoot":"","sources":["../src/turnState.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAc9C,MAAM,CAAC,MAAM,MAAM,GAAoB,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC5D,MAAM,CAAC,MAAM,IAAI,GAAkB;IAClC,MAAM,EAAE,MAAM;IACd,OAAO,EAAE,EAAE;IACX,OAAO,EAAE,KAAK;CACd,CAAC;AACF,MAAM,CAAC,MAAM,cAAc,GAAG,eAAe,CAAC,qDAAqD,CAAC,CAAC;AACrG,MAAM,UAAU,WAAW,CAAC,KAAoB,EAAE,IAAiB;IAClE,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;AACxD,CAAC;AACD,MAAM,UAAU,OAAO,CAAC,KAAoB;IAC3C,OAAO;QACN,GAAG,KAAK;QACR,OAAO,EAAE,IAAI;KACb,CAAC;AACH,CAAC;AACD,MAAM,UAAU,MAAM,CAAC,KAAoB;IAC1C,OAAO;QACN,GAAG,KAAK;QACR,OAAO,EAAE,KAAK;KACd,CAAC;AACH,CAAC;AACD,MAAM,UAAU,IAAI,CAAC,KAAgB;IACpC,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACxF,CAAC"}
|
package/dist/turns.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { BackendFailure } from "@shivaedev/agent/failures.ts";
|
|
3
|
+
import type { SessionFrame } from "#adapters/sessionFrames.ts";
|
|
4
|
+
import type { TurnRequests } from "#turnRequests.ts";
|
|
5
|
+
export interface TurnDriver {
|
|
6
|
+
readonly close: Effect.Effect<void>;
|
|
7
|
+
readonly interrupt: Effect.Effect<void, BackendFailure>;
|
|
8
|
+
readonly queue: (text: string) => Effect.Effect<void, BackendFailure>;
|
|
9
|
+
readonly steer: (text: string) => Effect.Effect<void, BackendFailure>;
|
|
10
|
+
readonly track: (frame: SessionFrame) => Effect.Effect<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare const makeTurnDriver: (requests: TurnRequests) => Effect.Effect<{
|
|
13
|
+
close: Effect.Effect<void, never, never>;
|
|
14
|
+
interrupt: Effect.Effect<void, import("@shivaedev/agent/failures.ts").BackendUnavailable | import("@shivaedev/agent/failures.ts").ProtocolFailure | import("@shivaedev/agent/failures.ts").Rejected | import("@shivaedev/agent/failures.ts").UnknownAcceptance, never>;
|
|
15
|
+
queue: (text: string) => Effect.Effect<void, import("@shivaedev/agent/failures.ts").BackendUnavailable | import("@shivaedev/agent/failures.ts").ProtocolFailure | import("@shivaedev/agent/failures.ts").Rejected | import("@shivaedev/agent/failures.ts").UnknownAcceptance, never>;
|
|
16
|
+
steer: (text: string) => Effect.Effect<void, import("@shivaedev/agent/failures.ts").BackendUnavailable | import("@shivaedev/agent/failures.ts").ProtocolFailure | import("@shivaedev/agent/failures.ts").Rejected | import("@shivaedev/agent/failures.ts").UnknownAcceptance, never>;
|
|
17
|
+
track: (frame: SessionFrame) => Effect.Effect<void, never, never>;
|
|
18
|
+
}, never, import("effect/Scope").Scope>;
|
|
19
|
+
//# sourceMappingURL=turns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turns.d.ts","sourceRoot":"","sources":["../src/turns.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,MAAM,EAAkB,MAAM,QAAQ,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAiBrD,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IACxD,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IACtE,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IACtE,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CAC7D;AACD,eAAO,MAAM,cAAc;;;;;;uCA6DzB,CAAC"}
|
package/dist/turns.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Deferred, Effect, Ref, Semaphore } from "effect";
|
|
2
|
+
import { closed, idle, rested, running, SESSION_CLOSED, sent, withPending, } from "#turnState.ts";
|
|
3
|
+
function refuse(next, failure) {
|
|
4
|
+
return Deferred.fail(next.accepted, failure).pipe(Effect.andThen(Effect.logWarning("opencode: queued prompt failed", failure)));
|
|
5
|
+
}
|
|
6
|
+
export const makeTurnDriver = Effect.fnUntraced(function* (requests) {
|
|
7
|
+
const state = yield* Ref.make(idle);
|
|
8
|
+
const gate = yield* Semaphore.make(1);
|
|
9
|
+
function sendNow(text) {
|
|
10
|
+
return requests.prompt(text).pipe(Effect.andThen(Ref.update(state, (current) => (current._tag === "closed" ? current : running(current)))));
|
|
11
|
+
}
|
|
12
|
+
function flush(current) {
|
|
13
|
+
const [next] = current.pending;
|
|
14
|
+
if (next === undefined) {
|
|
15
|
+
return Effect.void;
|
|
16
|
+
}
|
|
17
|
+
return sendNow(next.text).pipe(Effect.matchEffect({
|
|
18
|
+
onFailure: (failure) => Ref.update(state, sent).pipe(Effect.andThen(refuse(next, failure))),
|
|
19
|
+
onSuccess: () => Ref.update(state, sent).pipe(Effect.andThen(Deferred.succeed(next.accepted, undefined))),
|
|
20
|
+
}), Effect.asVoid);
|
|
21
|
+
}
|
|
22
|
+
function admit(text, accepted) {
|
|
23
|
+
return Effect.flatMap(Ref.get(state), (current) => {
|
|
24
|
+
if (current._tag === "closed") {
|
|
25
|
+
return Deferred.fail(accepted, SESSION_CLOSED);
|
|
26
|
+
}
|
|
27
|
+
if (current.running) {
|
|
28
|
+
return Ref.set(state, withPending(current, { accepted, text }));
|
|
29
|
+
}
|
|
30
|
+
return sendNow(text).pipe(Effect.matchEffect({
|
|
31
|
+
onFailure: (failure) => Deferred.fail(accepted, failure),
|
|
32
|
+
onSuccess: () => Deferred.succeed(accepted, undefined),
|
|
33
|
+
}));
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const queue = Effect.fnUntraced(function* (text) {
|
|
37
|
+
const accepted = yield* Deferred.make();
|
|
38
|
+
yield* gate.withPermit(admit(text, accepted));
|
|
39
|
+
yield* Deferred.await(accepted);
|
|
40
|
+
});
|
|
41
|
+
function steer(text) {
|
|
42
|
+
return Effect.flatMap(Ref.get(state), (current) => (current._tag === "closed" ? Effect.fail(SESSION_CLOSED) : sendNow(text)));
|
|
43
|
+
}
|
|
44
|
+
const settled = Effect.flatMap(Ref.get(state), (current) => current._tag === "closed" ? Effect.void : Ref.set(state, rested(current)).pipe(Effect.andThen(flush(rested(current)))));
|
|
45
|
+
const close = Ref.getAndSet(state, closed).pipe(Effect.flatMap((current) => current._tag === "closed"
|
|
46
|
+
? Effect.void
|
|
47
|
+
: Effect.forEach(current.pending, (waiting) => Deferred.fail(waiting.accepted, SESSION_CLOSED)).pipe(Effect.asVoid)));
|
|
48
|
+
yield* Effect.addFinalizer(() => close);
|
|
49
|
+
return {
|
|
50
|
+
close,
|
|
51
|
+
interrupt: Effect.asVoid(requests.abort),
|
|
52
|
+
queue,
|
|
53
|
+
steer: (text) => gate.withPermit(steer(text)),
|
|
54
|
+
track: (frame) => (frame.type === "session.idle" ? gate.withPermit(settled) : Effect.void),
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=turns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turns.js","sourceRoot":"","sources":["../src/turns.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shivaedev/agent-opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "OpenCode integration for Effect applications",
|
|
5
5
|
"homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/agent-opencode#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -16,14 +16,15 @@
|
|
|
16
16
|
"type": "module",
|
|
17
17
|
"imports": {
|
|
18
18
|
"#*.ts": {
|
|
19
|
-
"source": "./src/*.ts",
|
|
19
|
+
"@shivaedev/source": "./src/*.ts",
|
|
20
20
|
"types": "./dist/*.d.ts",
|
|
21
21
|
"default": "./dist/*.js"
|
|
22
|
-
}
|
|
22
|
+
},
|
|
23
|
+
"#test/*": "./src/test-support/*"
|
|
23
24
|
},
|
|
24
25
|
"exports": {
|
|
25
26
|
"./*.ts": {
|
|
26
|
-
"source": "./src/*.ts",
|
|
27
|
+
"@shivaedev/source": "./src/*.ts",
|
|
27
28
|
"types": "./dist/*.d.ts",
|
|
28
29
|
"default": "./dist/*.js"
|
|
29
30
|
},
|
|
@@ -32,12 +33,39 @@
|
|
|
32
33
|
"files": [
|
|
33
34
|
"dist",
|
|
34
35
|
"src",
|
|
36
|
+
"!src/**/*.test.ts",
|
|
37
|
+
"!src/**/*.spec.ts",
|
|
38
|
+
"!src/**/test-support",
|
|
35
39
|
"CHANGELOG.md",
|
|
36
40
|
"README.md"
|
|
37
41
|
],
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@shivaedev/agent": "0.3.0",
|
|
44
|
+
"@shivaedev/http": "0.6.0"
|
|
45
|
+
},
|
|
38
46
|
"devDependencies": {
|
|
47
|
+
"@effect/platform-node": "4.0.6",
|
|
48
|
+
"@effect/vitest": "4.0.6",
|
|
49
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
50
|
+
"@shivaedev/quality": "0.60.0",
|
|
51
|
+
"@shivaedev/test-story": "0.38.0",
|
|
39
52
|
"@types/node": "24.19.2",
|
|
40
|
-
"
|
|
53
|
+
"effect": "4.0.6",
|
|
54
|
+
"typescript": "7.0.2",
|
|
55
|
+
"vitest": "5.0.5"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"@effect/platform-node": "4.0.6",
|
|
59
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
60
|
+
"effect": "4.0.6"
|
|
61
|
+
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"@effect/platform-node": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"@modelcontextprotocol/sdk": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
41
69
|
},
|
|
42
70
|
"engines": {
|
|
43
71
|
"node": ">=24"
|
|
@@ -48,7 +76,8 @@
|
|
|
48
76
|
},
|
|
49
77
|
"scripts": {
|
|
50
78
|
"build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" && tsc --project tsconfig.build.json",
|
|
51
|
-
"ready": "pnpm typecheck && pnpm build",
|
|
79
|
+
"ready": "pnpm typecheck && pnpm test && pnpm build",
|
|
80
|
+
"test": "vitest run --configLoader native",
|
|
52
81
|
"typecheck": "tsc --noEmit"
|
|
53
82
|
}
|
|
54
83
|
}
|
package/src/Provider.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Context, type Effect, type PubSub } from "effect";
|
|
2
|
+
import type { BackendFailure, ReadFailure } from "@shivaedev/agent/failures.ts";
|
|
3
|
+
import type { OpencodeRequest } from "#adapters/OpencodeRequest.ts";
|
|
4
|
+
import type { ToolSessions } from "#adapters/toolSessions.ts";
|
|
5
|
+
|
|
6
|
+
export interface ProviderPort {
|
|
7
|
+
readonly exited: Effect.Effect<void>;
|
|
8
|
+
readonly frames: PubSub.PubSub<unknown>;
|
|
9
|
+
readonly get: (request: OpencodeRequest) => Effect.Effect<unknown, ReadFailure>;
|
|
10
|
+
readonly post: (request: OpencodeRequest) => Effect.Effect<unknown, BackendFailure>;
|
|
11
|
+
readonly tools: ToolSessions;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class Provider extends Context.Service<Provider, ProviderPort>()("@shivaedev/agent-opencode/Provider") {}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
export const MessageUpdatedProperties = Schema.Struct({
|
|
3
|
+
info: Schema.Struct({
|
|
4
|
+
id: Schema.String,
|
|
5
|
+
"modelID": Schema.optional(Schema.String),
|
|
6
|
+
"providerID": Schema.optional(Schema.String),
|
|
7
|
+
role: Schema.Literals(["assistant", "user"]),
|
|
8
|
+
}),
|
|
9
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
3
|
+
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
4
|
+
import type { CallToolRequest, CallToolResult, ServerNotification, ServerRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
import { Effect, Option, Schema } from "effect";
|
|
7
|
+
import { CALLER_CALL, CALLER_SESSION, TOOL_SERVER_NAME } from "#adapters/toolNames.ts";
|
|
8
|
+
import type { ToolSessions } from "#adapters/toolSessions.ts";
|
|
9
|
+
import type { ToolDefinition } from "#tools.ts";
|
|
10
|
+
|
|
11
|
+
function listed(tool: ToolDefinition) {
|
|
12
|
+
return {
|
|
13
|
+
description: tool.description,
|
|
14
|
+
inputSchema: { ...tool.inputSchema, type: "object" as const },
|
|
15
|
+
name: tool.name,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function said(text: string, ok: boolean): CallToolResult {
|
|
19
|
+
return {
|
|
20
|
+
content: [{ text, type: "text" as const }],
|
|
21
|
+
isError: !ok,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const decodeArguments = Schema.decodeUnknownOption(Schema.Record(Schema.String, Schema.Unknown));
|
|
25
|
+
function caller(args: unknown): {
|
|
26
|
+
readonly rest: unknown;
|
|
27
|
+
readonly session: string | undefined;
|
|
28
|
+
readonly callId: string | undefined;
|
|
29
|
+
} {
|
|
30
|
+
return Option.match(decodeArguments(args), {
|
|
31
|
+
onNone: () => ({ callId: undefined, rest: args, session: undefined }),
|
|
32
|
+
onSome: ({ [CALLER_SESSION]: session, [CALLER_CALL]: callId, ...rest }) => ({
|
|
33
|
+
callId: typeof callId === "string" ? callId : undefined,
|
|
34
|
+
rest,
|
|
35
|
+
session: typeof session === "string" ? session : undefined,
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async function answerCall(sessions: ToolSessions, name: string, args: unknown): Promise<CallToolResult> {
|
|
40
|
+
const { rest, session, callId } = caller(args);
|
|
41
|
+
if (session === undefined) {
|
|
42
|
+
return said(`the call named no ${CALLER_SESSION}, so platform cannot tell which session is asking`, false);
|
|
43
|
+
}
|
|
44
|
+
if (callId === undefined) {
|
|
45
|
+
return said(`the call named no ${CALLER_CALL}, so platform cannot identify the provider call`, false);
|
|
46
|
+
}
|
|
47
|
+
const served = sessions.served(session);
|
|
48
|
+
if (Option.isNone(served)) {
|
|
49
|
+
return said(`platform serves no open session ${session}`, false);
|
|
50
|
+
}
|
|
51
|
+
const tool = served.value.get(name);
|
|
52
|
+
if (tool === undefined) {
|
|
53
|
+
return said(`session ${session} was given no tool named ${name}`, false);
|
|
54
|
+
}
|
|
55
|
+
const outcome = await tool(callId, rest);
|
|
56
|
+
return said(outcome.text, outcome.ok);
|
|
57
|
+
}
|
|
58
|
+
const progressWhileOpen = Effect.fnUntraced(function* (send: (progress: number) => Promise<void>) {
|
|
59
|
+
let progress = 0;
|
|
60
|
+
const report = Effect.promise(() =>
|
|
61
|
+
send(progress).then(() => {
|
|
62
|
+
progress += 1;
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
yield* report;
|
|
66
|
+
yield* Effect.forkScoped(Effect.forever(Effect.andThen(Effect.sleep(30_000), report)));
|
|
67
|
+
});
|
|
68
|
+
function answerWithProgress(sessions: ToolSessions, message: CallToolRequest, extra: RequestHandlerExtra<ServerRequest, ServerNotification>) {
|
|
69
|
+
return Effect.runPromise(
|
|
70
|
+
Effect.scoped(
|
|
71
|
+
Effect.gen(function* () {
|
|
72
|
+
const token = message.params._meta?.progressToken;
|
|
73
|
+
if (token !== undefined) {
|
|
74
|
+
yield* progressWhileOpen((progress) =>
|
|
75
|
+
extra.sendNotification({ method: "notifications/progress", params: { progress, progressToken: token } }),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return yield* Effect.promise(() => answerCall(sessions, message.params.name, message.params.arguments));
|
|
79
|
+
}),
|
|
80
|
+
),
|
|
81
|
+
{ signal: extra.signal },
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
// Omitting the session-id generator makes the transport stateless, so each request gets its own protocol server and nothing is carried between them.
|
|
85
|
+
export function answerToolRequest(tools: readonly ToolDefinition[], sessions: ToolSessions) {
|
|
86
|
+
const list = tools.map(listed);
|
|
87
|
+
return async (request: Request): Promise<Response> => {
|
|
88
|
+
const server = new Server({ name: TOOL_SERVER_NAME, version: "0.0.0" }, { capabilities: { tools: {} } });
|
|
89
|
+
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: list }));
|
|
90
|
+
server.setRequestHandler(CallToolRequestSchema, (message, extra) => answerWithProgress(sessions, message, extra));
|
|
91
|
+
const transport = new WebStandardStreamableHTTPServerTransport();
|
|
92
|
+
await server.connect(transport);
|
|
93
|
+
const response = await transport.handleRequest(request);
|
|
94
|
+
if (response.body === null) {
|
|
95
|
+
await server.close();
|
|
96
|
+
return response;
|
|
97
|
+
}
|
|
98
|
+
const reader = response.body.getReader();
|
|
99
|
+
const body = new ReadableStream<Uint8Array>({
|
|
100
|
+
async cancel() {
|
|
101
|
+
await reader.cancel();
|
|
102
|
+
await server.close();
|
|
103
|
+
},
|
|
104
|
+
async pull(controller) {
|
|
105
|
+
try {
|
|
106
|
+
const next = await reader.read();
|
|
107
|
+
if (next.done) {
|
|
108
|
+
await server.close();
|
|
109
|
+
controller.close();
|
|
110
|
+
} else {
|
|
111
|
+
controller.enqueue(next.value);
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
await server.close();
|
|
115
|
+
controller.error(error);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
return new Response(body, { headers: response.headers, status: response.status });
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Deferred, Effect, PubSub, type Scope, Stream } from "effect";
|
|
2
|
+
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/http";
|
|
3
|
+
import { type ReadFailure, Rejected } from "@shivaedev/agent/failures.ts";
|
|
4
|
+
import { checkStatus } from "@shivaedev/http/checkStatus.ts";
|
|
5
|
+
import { filterStatus } from "@shivaedev/http/filterStatus.ts";
|
|
6
|
+
import type { OpencodeRequest } from "#adapters/OpencodeRequest.ts";
|
|
7
|
+
import { openSseBuffer } from "#adapters/openSseBuffer.ts";
|
|
8
|
+
import type { ToolSessions } from "#adapters/toolSessions.ts";
|
|
9
|
+
import { opencodeFailure, unavailable } from "#failure.ts";
|
|
10
|
+
import type { ProviderPort } from "#Provider.ts";
|
|
11
|
+
|
|
12
|
+
function checkedPost(response: HttpClientResponse.HttpClientResponse, request: OpencodeRequest) {
|
|
13
|
+
return HttpClientResponse.matchStatus(response, {
|
|
14
|
+
"4xx": (refused) =>
|
|
15
|
+
Effect.fail(
|
|
16
|
+
new Rejected({
|
|
17
|
+
backend: "opencode",
|
|
18
|
+
message: `POST ${request.path} refused with HTTP ${refused.status}`,
|
|
19
|
+
providerCode: String(refused.status),
|
|
20
|
+
}),
|
|
21
|
+
),
|
|
22
|
+
orElse: (answered) => checkStatus(answered).pipe(Effect.as(answered)),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export const makeOpencodeServer = Effect.fn("OpenCode.makeOpencodeServer")(function* (
|
|
26
|
+
url: string,
|
|
27
|
+
tools: ToolSessions,
|
|
28
|
+
): Effect.fn.Return<ProviderPort, ReadFailure, Scope.Scope | HttpClient.HttpClient> {
|
|
29
|
+
const native = yield* HttpClient.HttpClient;
|
|
30
|
+
const client = filterStatus(native);
|
|
31
|
+
const frames = yield* PubSub.unbounded<unknown>();
|
|
32
|
+
const exited = yield* Deferred.make<void>();
|
|
33
|
+
const ready = yield* Deferred.make<void, ReadFailure>();
|
|
34
|
+
const buffer = openSseBuffer(() => undefined);
|
|
35
|
+
const listen = client.get(new URL("/global/event", url).href).pipe(
|
|
36
|
+
Effect.mapError(unavailable),
|
|
37
|
+
Effect.flatMap((response) =>
|
|
38
|
+
Deferred.succeed(ready, undefined).pipe(
|
|
39
|
+
Effect.andThen(
|
|
40
|
+
response.stream.pipe(
|
|
41
|
+
Stream.decodeText,
|
|
42
|
+
Stream.flatMap((text) => Stream.fromIterable(buffer.take(text))),
|
|
43
|
+
Stream.runForEach((frame) => PubSub.publish(frames, frame)),
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
),
|
|
47
|
+
),
|
|
48
|
+
Effect.catch((error) => Deferred.fail(ready, unavailable(error))),
|
|
49
|
+
Effect.ensuring(Deferred.succeed(exited, undefined)),
|
|
50
|
+
);
|
|
51
|
+
yield* Effect.forkScoped(listen);
|
|
52
|
+
yield* Deferred.await(ready);
|
|
53
|
+
function call(method: "GET" | "POST") {
|
|
54
|
+
return (request: OpencodeRequest) => {
|
|
55
|
+
const address = new URL(request.path, url);
|
|
56
|
+
for (const [key, value] of Object.entries(request.query)) {
|
|
57
|
+
address.searchParams.set(key, value);
|
|
58
|
+
}
|
|
59
|
+
const req = HttpClientRequest.make(method)(address.href);
|
|
60
|
+
const send =
|
|
61
|
+
method === "POST"
|
|
62
|
+
? native.execute(HttpClientRequest.bodyJsonUnsafe(req, request.body)).pipe(Effect.flatMap((response) => checkedPost(response, request)))
|
|
63
|
+
: client.execute(req);
|
|
64
|
+
return send.pipe(
|
|
65
|
+
Effect.mapError((error) => (error._tag === "Rejected" ? error : unavailable(error))),
|
|
66
|
+
Effect.flatMap((response) => (response.status === 204 ? Effect.succeed(undefined) : response.json.pipe(Effect.mapError(opencodeFailure)))),
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
exited: Deferred.await(exited),
|
|
72
|
+
frames,
|
|
73
|
+
get: (request) => call("GET")(request).pipe(Effect.catchTag("Rejected", (error) => Effect.fail(opencodeFailure(error.message)))),
|
|
74
|
+
post: call("POST"),
|
|
75
|
+
tools,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Raw } from "@shivaedev/agent/eventData.ts";
|
|
2
|
+
import type { RunEvent as AgentEvent } from "@shivaedev/agent/events.ts";
|
|
3
|
+
|
|
4
|
+
type RawPayload = typeof Raw.Type;
|
|
5
|
+
export function rawOf(kind: string, payload: unknown): RawPayload {
|
|
6
|
+
return {
|
|
7
|
+
kind,
|
|
8
|
+
payload: JSON.stringify(payload),
|
|
9
|
+
source: "opencode",
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function rawEvent(raw: RawPayload): AgentEvent[] {
|
|
13
|
+
return [{ raw, type: "raw" }];
|
|
14
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Option, Schema } from "effect";
|
|
2
|
+
import { MessageUpdatedProperties } from "#adapters/MessageUpdatedProperties.ts";
|
|
3
|
+
export interface MessageAuthor {
|
|
4
|
+
readonly model: string | undefined;
|
|
5
|
+
readonly role: "agent" | "user";
|
|
6
|
+
}
|
|
7
|
+
export interface MessageAuthors {
|
|
8
|
+
readonly of: (messageId: string) => Option.Option<MessageAuthor>;
|
|
9
|
+
readonly record: (properties: unknown) => void;
|
|
10
|
+
}
|
|
11
|
+
const decodeMessage = Schema.decodeUnknownOption(MessageUpdatedProperties);
|
|
12
|
+
function modelOf(info: typeof MessageUpdatedProperties.Type.info, sessionModel: string | undefined) {
|
|
13
|
+
return info.providerID === undefined || info.modelID === undefined ? sessionModel : `${info.providerID}/${info.modelID}`;
|
|
14
|
+
}
|
|
15
|
+
export function openMessageAuthors(sessionModel: string | undefined): MessageAuthors {
|
|
16
|
+
const authors = new Map<string, MessageAuthor>();
|
|
17
|
+
return {
|
|
18
|
+
of: (messageId) => Option.fromNullishOr(authors.get(messageId)),
|
|
19
|
+
record: (properties) => {
|
|
20
|
+
Option.match(decodeMessage(properties), {
|
|
21
|
+
onNone: () => undefined,
|
|
22
|
+
onSome: ({ info }) => {
|
|
23
|
+
authors.set(info.id, {
|
|
24
|
+
model: modelOf(info, sessionModel),
|
|
25
|
+
role: info.role === "user" ? "user" : "agent",
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Option, Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
const decodeFrame = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Unknown));
|
|
4
|
+
function dataFrames(chunk: string, onMalformed: (line: string) => void): unknown[] {
|
|
5
|
+
const frames: unknown[] = [];
|
|
6
|
+
for (const line of chunk.split("\n")) {
|
|
7
|
+
if (!line.startsWith("data:")) {
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
const frame = decodeFrame(line.slice(5));
|
|
11
|
+
if (Option.isSome(frame)) {
|
|
12
|
+
frames.push(frame.value);
|
|
13
|
+
} else {
|
|
14
|
+
onMalformed(line);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return frames;
|
|
18
|
+
}
|
|
19
|
+
interface SseBuffer {
|
|
20
|
+
readonly take: (chunk: string) => unknown[];
|
|
21
|
+
}
|
|
22
|
+
export function openSseBuffer(onMalformed: (line: string) => void): SseBuffer {
|
|
23
|
+
let pending = "";
|
|
24
|
+
return {
|
|
25
|
+
take: (chunk) => {
|
|
26
|
+
pending += chunk;
|
|
27
|
+
const boundary = pending.lastIndexOf("\n");
|
|
28
|
+
if (boundary < 0) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
const complete = pending.slice(0, boundary);
|
|
32
|
+
pending = pending.slice(boundary + 1);
|
|
33
|
+
return dataFrames(complete, onMalformed);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Raw } from "@shivaedev/agent/eventData.ts";
|
|
2
|
+
import type { RunEvent as AgentEvent } from "@shivaedev/agent/events.ts";
|
|
3
|
+
|
|
4
|
+
type RawPayload = typeof Raw.Type;
|
|
5
|
+
type SessionState = "running" | "awaiting-input";
|
|
6
|
+
|
|
7
|
+
import { Option, Schema } from "effect";
|
|
8
|
+
import { rawEvent } from "#adapters/mapping.ts";
|
|
9
|
+
import { SessionErrorProperties, SessionStatusProperties } from "#adapters/protocol.ts";
|
|
10
|
+
|
|
11
|
+
const decodeStatus = Schema.decodeUnknownOption(SessionStatusProperties);
|
|
12
|
+
const decodeError = Schema.decodeUnknownOption(SessionErrorProperties);
|
|
13
|
+
function stateOf(type: string): SessionState | undefined {
|
|
14
|
+
if (type === "idle") {
|
|
15
|
+
return "awaiting-input";
|
|
16
|
+
}
|
|
17
|
+
return type === "busy" || type === "retry" ? "running" : undefined;
|
|
18
|
+
}
|
|
19
|
+
const ABORTED = "MessageAbortedError";
|
|
20
|
+
interface TurnBoundary {
|
|
21
|
+
readonly errored: (raw: RawPayload, properties: unknown) => AgentEvent[];
|
|
22
|
+
readonly idled: (raw: RawPayload) => AgentEvent[];
|
|
23
|
+
readonly status: (raw: RawPayload, properties: unknown) => AgentEvent[];
|
|
24
|
+
}
|
|
25
|
+
export function openTurnBoundary(): TurnBoundary {
|
|
26
|
+
let running = false;
|
|
27
|
+
let outcome: "failed" | "interrupted" | undefined;
|
|
28
|
+
return {
|
|
29
|
+
errored: (raw, properties) => {
|
|
30
|
+
outcome = Option.match(decodeError(properties), {
|
|
31
|
+
onNone: () => "failed" as const,
|
|
32
|
+
onSome: ({ error }) => (error?.name === ABORTED ? "interrupted" : "failed"),
|
|
33
|
+
});
|
|
34
|
+
return rawEvent(raw);
|
|
35
|
+
},
|
|
36
|
+
idled: (raw) => {
|
|
37
|
+
if (!running) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
running = false;
|
|
41
|
+
const status = outcome ?? "completed";
|
|
42
|
+
outcome = undefined;
|
|
43
|
+
return [{ raw, state: status, type: "execution" }];
|
|
44
|
+
},
|
|
45
|
+
status: (raw, properties) =>
|
|
46
|
+
Option.match(decodeStatus(properties), {
|
|
47
|
+
onNone: () => rawEvent(raw),
|
|
48
|
+
onSome: ({ status }) => {
|
|
49
|
+
const state = stateOf(status.type);
|
|
50
|
+
if (state === undefined) {
|
|
51
|
+
return rawEvent(raw);
|
|
52
|
+
}
|
|
53
|
+
running = running || state === "running";
|
|
54
|
+
return [{ raw, state, type: "execution" }];
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export default () => ({
|
|
2
|
+
"tool.execute.before": (
|
|
3
|
+
input: {
|
|
4
|
+
tool: string;
|
|
5
|
+
"sessionID": string;
|
|
6
|
+
"callID": string;
|
|
7
|
+
},
|
|
8
|
+
output: {
|
|
9
|
+
args: Record<string, unknown>;
|
|
10
|
+
},
|
|
11
|
+
) => {
|
|
12
|
+
if (input.tool.startsWith("platform_")) {
|
|
13
|
+
output.args.callerSession = input.sessionID;
|
|
14
|
+
output.args.callerCall = input.callID;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
});
|