t3code-cli 0.1.1 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "t3code-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "CLI for t3code",
5
5
  "keywords": [
6
6
  "claude",
@@ -32,13 +32,15 @@
32
32
  "types": "./src/index.ts"
33
33
  }
34
34
  },
35
+ "dependencies": {},
35
36
  "devDependencies": {
36
37
  "@changesets/cli": "^2.31.0",
37
38
  "@effect/platform-node": "4.0.0-beta.74",
38
39
  "@types/node": "^25.9.1",
39
40
  "effect": "4.0.0-beta.74",
40
41
  "typescript": "^6.0.3",
41
- "vite-plus": "^0.1.24"
42
+ "vite-plus": "^0.1.24",
43
+ "@t3tools/contracts": "0.0.24"
42
44
  },
43
45
  "engines": {
44
46
  "node": "^24.0.0"
@@ -52,7 +54,7 @@
52
54
  "lint:fix": "vp lint --fix",
53
55
  "changeset": "changeset",
54
56
  "release:check": "pnpm check && pnpm typecheck && pnpm pack --dry-run",
55
- "release:publish": "pnpm publish --access public --no-git-checks",
57
+ "release:publish": "changeset publish",
56
58
  "typecheck": "tsc --noEmit"
57
59
  }
58
60
  }
@@ -1,4 +1,7 @@
1
1
  import * as Effect from "effect/Effect";
2
+ import { ModelSelection, ProviderInstanceId } from "@t3tools/contracts";
3
+ import type { OrchestrationProjectShell } from "@t3tools/contracts";
4
+ import * as Schema from "effect/Schema";
2
5
 
3
6
  import { ModelSelectionError } from "../domain/error.ts";
4
7
  import {
@@ -6,23 +9,18 @@ import {
6
9
  firstSelectableModel,
7
10
  firstSelectableProvider,
8
11
  } from "../domain/model-config.ts";
9
- import {
10
- decodeModelSelection,
11
- type ModelSelection,
12
- type ProjectShell,
13
- type ServerConfig,
14
- } from "../domain/schema.ts";
12
+ import type { ServerConfigForCli } from "../orchestration/service.ts";
15
13
  import type { StartThreadInput } from "./service.ts";
16
14
 
17
15
  export function resolveModelSelection(input: {
18
16
  readonly start: StartThreadInput;
19
- readonly project: ProjectShell;
20
- readonly serverConfig: ServerConfig;
17
+ readonly project: OrchestrationProjectShell;
18
+ readonly serverConfig: ServerConfigForCli;
21
19
  }) {
22
20
  return Effect.gen(function* () {
23
21
  if (input.start.provider !== undefined && input.start.model !== undefined) {
24
22
  return withModelOptions(input.start, {
25
- instanceId: input.start.provider,
23
+ instanceId: ProviderInstanceId.make(input.start.provider),
26
24
  model: input.start.model,
27
25
  });
28
26
  }
@@ -33,7 +31,7 @@ export function resolveModelSelection(input: {
33
31
  return yield* failNoAvailableModel();
34
32
  }
35
33
  return withModelOptions(input.start, {
36
- instanceId: input.start.provider,
34
+ instanceId: ProviderInstanceId.make(input.start.provider),
37
35
  model: model.slug,
38
36
  });
39
37
  }
@@ -70,7 +68,7 @@ export function mergeModelOptions(
70
68
  for (const option of options) {
71
69
  optionsById.set(option.id, option);
72
70
  }
73
- return decodeModelSelection({
71
+ return Schema.decodeUnknownSync(ModelSelection)({
74
72
  ...selection,
75
73
  options: [...optionsById.values()],
76
74
  });
@@ -83,8 +81,8 @@ function withModelOptions(input: StartThreadInput, selection: ModelSelection): M
83
81
  return mergeModelOptions(selection, input.options);
84
82
  }
85
83
 
86
- function firstAvailableModel(serverConfig: ServerConfig) {
87
- const providers = serverConfig.providers ?? [];
84
+ function firstAvailableModel(serverConfig: ServerConfigForCli) {
85
+ const providers = serverConfig.providers;
88
86
  const provider = firstSelectableProvider(providers);
89
87
  if (provider === undefined) {
90
88
  return failNoAvailableModel();
@@ -92,8 +90,8 @@ function firstAvailableModel(serverConfig: ServerConfig) {
92
90
  return Effect.succeed(provider);
93
91
  }
94
92
 
95
- function findProvider(serverConfig: ServerConfig, instanceId: string) {
96
- const provider = findSelectableProvider(serverConfig.providers ?? [], instanceId);
93
+ function findProvider(serverConfig: ServerConfigForCli, instanceId: string) {
94
+ const provider = findSelectableProvider(serverConfig.providers, instanceId);
97
95
  if (provider === undefined) {
98
96
  return failNoAvailableModel();
99
97
  }
@@ -12,7 +12,7 @@ export const makeModelsApplication = Effect.fn("makeModelsApplication")(function
12
12
  }) {
13
13
  const config = yield* orchestration.getServerConfig();
14
14
  return filterProvidersForModelListing({
15
- providers: config.providers ?? [],
15
+ providers: config.providers,
16
16
  all: input.all === true,
17
17
  ...(input.provider !== undefined && input.provider.length > 0
18
18
  ? { provider: input.provider }
@@ -2,9 +2,9 @@ import * as Crypto from "effect/Crypto";
2
2
  import * as DateTime from "effect/DateTime";
3
3
  import * as Effect from "effect/Effect";
4
4
  import * as Path from "effect/Path";
5
+ import { CommandId, ProjectId, type ClientOrchestrationCommand } from "@t3tools/contracts";
5
6
 
6
7
  import { Environment } from "../environment/service.ts";
7
- import type { ProjectCreateCommand } from "../domain/command-schema.ts";
8
8
 
9
9
  export const makeProjectCreateCommand = Effect.fn("makeProjectCreateCommand")(function* (input: {
10
10
  readonly path: string;
@@ -14,15 +14,17 @@ export const makeProjectCreateCommand = Effect.fn("makeProjectCreateCommand")(fu
14
14
  const crypto = yield* Crypto.Crypto;
15
15
  const environment = yield* Environment;
16
16
  const workspaceRoot = path.resolve(environment.cwd, input.path);
17
- const projectId = yield* crypto.randomUUIDv4.pipe(Effect.orDie);
17
+ const projectId = ProjectId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
18
18
  const title = input.title?.trim();
19
19
  const createdAt = DateTime.formatIso(yield* DateTime.now);
20
20
  return {
21
21
  type: "project.create",
22
- commandId: `t3cli:project-create:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
22
+ commandId: CommandId.make(
23
+ `t3cli:project-create:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
24
+ ),
23
25
  projectId,
24
26
  title: title !== undefined && title.length > 0 ? title : path.basename(workspaceRoot),
25
27
  workspaceRoot,
26
28
  createdAt,
27
- } satisfies ProjectCreateCommand & { readonly projectId: string };
29
+ } satisfies Extract<ClientOrchestrationCommand, { readonly type: "project.create" }>;
28
30
  });
@@ -1,18 +1,18 @@
1
1
  import * as Context from "effect/Context";
2
2
  import type * as Effect from "effect/Effect";
3
3
  import type * as Stream from "effect/Stream";
4
-
5
- import type { ApplicationError } from "./error.ts";
6
- import type { DispatchResult } from "../domain/command-schema.ts";
7
4
  import type {
8
- ProjectShell,
9
- ServerProvider,
10
- ShellSnapshot,
11
- ThreadDetail,
12
- ThreadMessage,
13
- ThreadShell,
5
+ DispatchResult,
14
6
  ModelSelection,
15
- } from "../domain/schema.ts";
7
+ OrchestrationMessage,
8
+ OrchestrationProjectShell,
9
+ OrchestrationShellSnapshot,
10
+ OrchestrationThread,
11
+ OrchestrationThreadShell,
12
+ ServerProvider,
13
+ } from "@t3tools/contracts";
14
+
15
+ import type { ApplicationError } from "./error.ts";
16
16
 
17
17
  export type StartThreadInput = {
18
18
  readonly projectRef: string;
@@ -35,15 +35,15 @@ export type StartThreadPolicy = {
35
35
  };
36
36
 
37
37
  export type WaitEvent =
38
- | { readonly type: "thread"; readonly thread: ThreadDetail }
39
- | { readonly type: "message"; readonly message: ThreadMessage }
38
+ | { readonly type: "thread"; readonly thread: OrchestrationThread }
39
+ | { readonly type: "message"; readonly message: OrchestrationMessage }
40
40
  | { readonly type: "status"; readonly status: string; readonly threadId: string }
41
- | { readonly type: "done"; readonly thread: ThreadDetail };
41
+ | { readonly type: "done"; readonly thread: OrchestrationThread };
42
42
 
43
43
  export class T3Application extends Context.Service<
44
44
  T3Application,
45
45
  {
46
- readonly loadShell: () => Effect.Effect<ShellSnapshot, ApplicationError>;
46
+ readonly loadShell: () => Effect.Effect<OrchestrationShellSnapshot, ApplicationError>;
47
47
  readonly listModels: (input: {
48
48
  readonly all?: boolean;
49
49
  readonly provider?: string;
@@ -52,17 +52,19 @@ export class T3Application extends Context.Service<
52
52
  readonly path: string;
53
53
  readonly title?: string;
54
54
  }) => Effect.Effect<
55
- { readonly dispatch: DispatchResult; readonly project: ProjectShell },
55
+ { readonly dispatch: DispatchResult; readonly project: OrchestrationProjectShell },
56
56
  ApplicationError
57
57
  >;
58
58
  readonly listThreads: (projectRef: string) => Effect.Effect<
59
59
  {
60
- readonly project: ProjectShell;
61
- readonly threads: ReadonlyArray<ThreadShell>;
60
+ readonly project: OrchestrationProjectShell;
61
+ readonly threads: ReadonlyArray<OrchestrationThreadShell>;
62
62
  },
63
63
  ApplicationError
64
64
  >;
65
- readonly getThreadMessages: (threadId: string) => Effect.Effect<ThreadDetail, ApplicationError>;
65
+ readonly getThreadMessages: (
66
+ threadId: string,
67
+ ) => Effect.Effect<OrchestrationThread, ApplicationError>;
66
68
  readonly archiveThread: (threadId: string) => Effect.Effect<DispatchResult, ApplicationError>;
67
69
  readonly startThread: (
68
70
  input: StartThreadInput,
@@ -70,9 +72,9 @@ export class T3Application extends Context.Service<
70
72
  ) => Effect.Effect<
71
73
  {
72
74
  readonly dispatch: DispatchResult;
73
- readonly project: ProjectShell;
75
+ readonly project: OrchestrationProjectShell;
74
76
  readonly threadId: string;
75
- readonly thread?: ThreadDetail;
77
+ readonly thread?: OrchestrationThread;
76
78
  },
77
79
  ApplicationError
78
80
  >;
@@ -83,11 +85,13 @@ export class T3Application extends Context.Service<
83
85
  {
84
86
  readonly dispatch: DispatchResult;
85
87
  readonly threadId: string;
86
- readonly thread?: ThreadDetail;
88
+ readonly thread?: OrchestrationThread;
87
89
  },
88
90
  ApplicationError
89
91
  >;
90
92
  readonly watchThread: (threadId: string) => Stream.Stream<WaitEvent, ApplicationError>;
91
- readonly waitForThread: (threadId: string) => Effect.Effect<ThreadDetail, ApplicationError>;
93
+ readonly waitForThread: (
94
+ threadId: string,
95
+ ) => Effect.Effect<OrchestrationThread, ApplicationError>;
92
96
  }
93
97
  >()("t3cli/T3Application") {}
@@ -1,10 +1,10 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Option from "effect/Option";
3
3
  import * as Stream from "effect/Stream";
4
+ import { ORCHESTRATION_WS_METHODS } from "@t3tools/contracts";
4
5
 
5
6
  import type { Orchestration } from "../orchestration/service.ts";
6
7
  import { RpcError } from "../rpc/error.ts";
7
- import { ORCHESTRATION_WS_METHODS } from "../protocol/schema.ts";
8
8
 
9
9
  export function waitForShellSequence(input: {
10
10
  readonly orchestration: Orchestration;
@@ -1,23 +1,25 @@
1
1
  import * as Crypto from "effect/Crypto";
2
2
  import * as DateTime from "effect/DateTime";
3
3
  import * as Effect from "effect/Effect";
4
-
5
- import type {
6
- ThreadArchiveCommand,
7
- ThreadCreateCommand,
8
- ThreadTurnStartCommand,
9
- } from "../domain/command-schema.ts";
10
- import type { ModelSelection, ProjectShell, ServerConfig } from "../domain/schema.ts";
4
+ import {
5
+ CommandId,
6
+ MessageId,
7
+ ThreadId,
8
+ type ClientOrchestrationCommand,
9
+ type ModelSelection,
10
+ type OrchestrationProjectShell,
11
+ } from "@t3tools/contracts";
12
+ import type { ServerConfigForCli } from "../orchestration/service.ts";
11
13
  import { resolveModelSelection } from "./model-selection.ts";
12
14
  import type { SendThreadInput, StartThreadInput } from "./service.ts";
13
15
 
14
16
  export const makeThreadStartCommands = Effect.fn("makeThreadStartCommands")(function* (input: {
15
17
  readonly start: StartThreadInput;
16
- readonly project: ProjectShell;
17
- readonly serverConfig: ServerConfig;
18
+ readonly project: OrchestrationProjectShell;
19
+ readonly serverConfig: ServerConfigForCli;
18
20
  }) {
19
21
  const crypto = yield* Crypto.Crypto;
20
- const threadId = yield* crypto.randomUUIDv4.pipe(Effect.orDie);
22
+ const threadId = ThreadId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie));
21
23
  const createdAt = DateTime.formatIso(yield* DateTime.now);
22
24
  const modelSelection = yield* resolveModelSelection(input);
23
25
  const inputTitle = input.start.title?.trim();
@@ -25,7 +27,9 @@ export const makeThreadStartCommands = Effect.fn("makeThreadStartCommands")(func
25
27
  const title = inputTitle !== undefined && inputTitle.length > 0 ? inputTitle : messageTitle;
26
28
  const createCommand = {
27
29
  type: "thread.create",
28
- commandId: `t3cli:thread-create:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
30
+ commandId: CommandId.make(
31
+ `t3cli:thread-create:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
32
+ ),
29
33
  threadId,
30
34
  projectId: input.project.id,
31
35
  title: title.length > 0 ? title : "New thread",
@@ -35,13 +39,15 @@ export const makeThreadStartCommands = Effect.fn("makeThreadStartCommands")(func
35
39
  branch: null,
36
40
  worktreePath: input.start.worktreePath ?? null,
37
41
  createdAt,
38
- } satisfies ThreadCreateCommand;
42
+ } satisfies Extract<ClientOrchestrationCommand, { readonly type: "thread.create" }>;
39
43
  const turnCommand = {
40
44
  type: "thread.turn.start",
41
- commandId: `t3cli:thread-start:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
45
+ commandId: CommandId.make(
46
+ `t3cli:thread-start:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
47
+ ),
42
48
  threadId,
43
49
  message: {
44
- messageId: yield* crypto.randomUUIDv4.pipe(Effect.orDie),
50
+ messageId: MessageId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie)),
45
51
  role: "user",
46
52
  text: input.start.message,
47
53
  attachments: [],
@@ -51,7 +57,7 @@ export const makeThreadStartCommands = Effect.fn("makeThreadStartCommands")(func
51
57
  runtimeMode: "full-access",
52
58
  interactionMode: "default",
53
59
  createdAt,
54
- } satisfies ThreadTurnStartCommand;
60
+ } satisfies Extract<ClientOrchestrationCommand, { readonly type: "thread.turn.start" }>;
55
61
  return { createCommand, turnCommand, threadId };
56
62
  });
57
63
 
@@ -62,10 +68,12 @@ export const makeThreadTurnContinueCommand = Effect.fn("makeThreadTurnContinueCo
62
68
  const createdAt = DateTime.formatIso(yield* DateTime.now);
63
69
  return {
64
70
  type: "thread.turn.start",
65
- commandId: `t3cli:thread-start:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
66
- threadId: input.threadId,
71
+ commandId: CommandId.make(
72
+ `t3cli:thread-start:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
73
+ ),
74
+ threadId: ThreadId.make(input.threadId),
67
75
  message: {
68
- messageId: yield* crypto.randomUUIDv4.pipe(Effect.orDie),
76
+ messageId: MessageId.make(yield* crypto.randomUUIDv4.pipe(Effect.orDie)),
69
77
  role: "user",
70
78
  text: input.message,
71
79
  attachments: [],
@@ -74,7 +82,7 @@ export const makeThreadTurnContinueCommand = Effect.fn("makeThreadTurnContinueCo
74
82
  interactionMode: "default",
75
83
  ...(input.modelSelection !== undefined ? { modelSelection: input.modelSelection } : {}),
76
84
  createdAt,
77
- } satisfies ThreadTurnStartCommand;
85
+ } satisfies Extract<ClientOrchestrationCommand, { readonly type: "thread.turn.start" }>;
78
86
  });
79
87
 
80
88
  export const makeThreadArchiveCommand = Effect.fn("makeThreadArchiveCommand")(function* (
@@ -83,7 +91,9 @@ export const makeThreadArchiveCommand = Effect.fn("makeThreadArchiveCommand")(fu
83
91
  const crypto = yield* Crypto.Crypto;
84
92
  return {
85
93
  type: "thread.archive",
86
- commandId: `t3cli:thread-archive:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
87
- threadId,
88
- } satisfies ThreadArchiveCommand;
94
+ commandId: CommandId.make(
95
+ `t3cli:thread-archive:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
96
+ ),
97
+ threadId: ThreadId.make(threadId),
98
+ } satisfies Extract<ClientOrchestrationCommand, { readonly type: "thread.archive" }>;
89
99
  });
@@ -1,10 +1,10 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Option from "effect/Option";
3
3
  import * as Stream from "effect/Stream";
4
+ import type { OrchestrationMessage, OrchestrationThread } from "@t3tools/contracts";
4
5
 
5
6
  import { ThreadSessionError } from "../domain/error.ts";
6
7
  import type { Orchestration } from "../orchestration/service.ts";
7
- import type { ThreadDetail, ThreadMessage } from "../domain/schema.ts";
8
8
  import type { WaitEvent } from "./service.ts";
9
9
  import {
10
10
  applyThreadEvent,
@@ -19,13 +19,13 @@ export function watchThread(input: {
19
19
  readonly orchestration: Orchestration;
20
20
  readonly threadId: string;
21
21
  }) {
22
- let current: ThreadDetail | undefined;
23
- let currentMessages: Map<string, ThreadMessage> | undefined;
22
+ let current: OrchestrationThread | undefined;
23
+ let currentMessages: Map<string, OrchestrationMessage> | undefined;
24
24
  return Stream.scoped(
25
25
  input.orchestration.watchThreadItems(input.threadId).pipe(
26
26
  Stream.flatMap((item) => {
27
27
  if (item.kind === "snapshot") {
28
- const messages = new Map<string, ThreadMessage>();
28
+ const messages = new Map<string, OrchestrationMessage>();
29
29
  for (const message of item.snapshot.thread.messages) {
30
30
  messages.set(messageKey(message), message);
31
31
  }
@@ -1,4 +1,4 @@
1
- import type { ServerProvider } from "../domain/schema.ts";
1
+ import type { ServerProvider } from "@t3tools/contracts";
2
2
 
3
3
  export function formatModelsHuman(providers: ReadonlyArray<ServerProvider>) {
4
4
  if (providers.length === 0) {
@@ -1,11 +1,11 @@
1
- import type { ProjectShell } from "../domain/schema.ts";
1
+ import type { OrchestrationProjectShell } from "@t3tools/contracts";
2
2
 
3
- export function formatProjectsHuman(projects: ReadonlyArray<ProjectShell>) {
3
+ export function formatProjectsHuman(projects: ReadonlyArray<OrchestrationProjectShell>) {
4
4
  return projects
5
5
  .map((project) => `- ${project.title}\n id: ${project.id}\n path: ${project.workspaceRoot}\n`)
6
6
  .join("");
7
7
  }
8
8
 
9
- export function formatProjectAddedHuman(project: ProjectShell) {
9
+ export function formatProjectAddedHuman(project: OrchestrationProjectShell) {
10
10
  return `project added: ${project.title}\nid: ${project.id}\npath: ${project.workspaceRoot}`;
11
11
  }
@@ -1,8 +1,8 @@
1
1
  import type { WaitEvent } from "../application/service.ts";
2
- import type { ThreadDetail, ThreadShell } from "../domain/schema.ts";
2
+ import type { OrchestrationThread, OrchestrationThreadShell } from "@t3tools/contracts";
3
3
  import { latestAssistantMessage, threadStatus } from "../domain/thread-lifecycle.ts";
4
4
 
5
- export function formatThreadsHuman(threads: ReadonlyArray<ThreadShell>) {
5
+ export function formatThreadsHuman(threads: ReadonlyArray<OrchestrationThreadShell>) {
6
6
  return threads
7
7
  .map(
8
8
  (thread) =>
@@ -12,23 +12,23 @@ export function formatThreadsHuman(threads: ReadonlyArray<ThreadShell>) {
12
12
  }
13
13
 
14
14
  export function formatThreadStartedHuman(input: {
15
- readonly thread: ThreadDetail;
15
+ readonly thread: OrchestrationThread;
16
16
  readonly sequence: number;
17
17
  }) {
18
18
  return `thread started: ${input.thread.title}\nid: ${input.thread.id}\nstatus: ${threadStatus(input.thread)}\nsequence: ${input.sequence}`;
19
19
  }
20
20
 
21
- export function formatThreadMessagesHuman(thread: ThreadDetail, limit: number) {
21
+ export function formatThreadMessagesHuman(thread: OrchestrationThread, limit: number) {
22
22
  const messages = limit === 0 ? thread.messages : thread.messages.slice(-limit);
23
23
  return messages.map((message) => `\n### ${message.role}\n\n${message.text}\n`).join("");
24
24
  }
25
25
 
26
- export function formatWaitDoneHuman(thread: ThreadDetail) {
26
+ export function formatWaitDoneHuman(thread: OrchestrationThread) {
27
27
  const latest = latestAssistantMessage(thread);
28
28
  return `status: ${threadStatus(thread)}\n${latest !== undefined ? `\n### ${latest.role}\n\n${latest.text}\n` : ""}`;
29
29
  }
30
30
 
31
- export function formatThreadMessagesJson(thread: ThreadDetail, full: boolean) {
31
+ export function formatThreadMessagesJson(thread: OrchestrationThread, full: boolean) {
32
32
  return full ? thread : { thread: stripThreadMessages(thread), messages: thread.messages };
33
33
  }
34
34
 
@@ -1,9 +1,14 @@
1
1
  import type * as Path from "effect/Path";
2
+ import type { OrchestrationProjectShell, OrchestrationShellSnapshot } from "@t3tools/contracts";
2
3
 
3
4
  import { ProjectLookupError } from "./error.ts";
4
- import type { ProjectShell, ShellSnapshot } from "./schema.ts";
5
5
 
6
- export function resolveProject(snapshot: ShellSnapshot, ref: string, path: Path.Path, cwd: string) {
6
+ export function resolveProject(
7
+ snapshot: OrchestrationShellSnapshot,
8
+ ref: string,
9
+ path: Path.Path,
10
+ cwd: string,
11
+ ) {
7
12
  const byId = findProjectById(snapshot, ref);
8
13
  if (byId !== null) {
9
14
  return byId;
@@ -18,6 +23,9 @@ export function resolveProject(snapshot: ShellSnapshot, ref: string, path: Path.
18
23
  throw new ProjectLookupError({ message: `project not found: ${ref}`, ref });
19
24
  }
20
25
 
21
- export function findProjectById(snapshot: ShellSnapshot, projectId: string): ProjectShell | null {
26
+ export function findProjectById(
27
+ snapshot: OrchestrationShellSnapshot,
28
+ projectId: string,
29
+ ): OrchestrationProjectShell | null {
22
30
  return snapshot.projects.find((project) => project.id === projectId) ?? null;
23
31
  }
@@ -1,4 +1,4 @@
1
- import type { ServerProvider } from "./schema.ts";
1
+ import type { ServerProvider } from "@t3tools/contracts";
2
2
 
3
3
  export function isSelectableProvider(provider: ServerProvider) {
4
4
  return provider.status === "ready" && provider.models.length > 0;
@@ -1,19 +1,13 @@
1
- import * as Schema from "effect/Schema";
2
-
3
1
  import {
4
- type ThreadDetail,
5
- type ThreadEvent,
6
- type ThreadMessage,
7
- type ThreadShell,
8
- ThreadMessageSchema,
9
- ThreadMessageSentEventSchema,
10
- ThreadSessionSetEventSchema,
11
- } from "./schema.ts";
12
-
13
- const isThreadMessageSentEvent = Schema.is(ThreadMessageSentEventSchema);
14
- const isThreadSessionSetEvent = Schema.is(ThreadSessionSetEventSchema);
2
+ OrchestrationMessage,
3
+ type OrchestrationEvent,
4
+ type OrchestrationMessage as OrchestrationMessageType,
5
+ type OrchestrationThread,
6
+ type OrchestrationThreadShell,
7
+ } from "@t3tools/contracts";
8
+ import * as Schema from "effect/Schema";
15
9
 
16
- export function isThreadActive(thread: ThreadShell | ThreadDetail) {
10
+ export function isThreadActive(thread: OrchestrationThreadShell | OrchestrationThread) {
17
11
  return (
18
12
  thread.session?.status === "starting" ||
19
13
  thread.session?.status === "running" ||
@@ -22,18 +16,18 @@ export function isThreadActive(thread: ThreadShell | ThreadDetail) {
22
16
  );
23
17
  }
24
18
 
25
- export function threadStatus(thread: ThreadShell | ThreadDetail) {
19
+ export function threadStatus(thread: OrchestrationThreadShell | OrchestrationThread) {
26
20
  if (isPendingStart(thread)) {
27
21
  return "pending";
28
22
  }
29
23
  return thread.session?.status ?? thread.latestTurn?.state ?? "unknown";
30
24
  }
31
25
 
32
- export function latestAssistantMessage(thread: ThreadDetail) {
26
+ export function latestAssistantMessage(thread: OrchestrationThread) {
33
27
  return thread.messages.toReversed().find((message) => message.role === "assistant");
34
28
  }
35
29
 
36
- export function isThreadCompleteEnough(thread: ThreadDetail) {
30
+ export function isThreadCompleteEnough(thread: OrchestrationThread) {
37
31
  if (thread.session?.status === "error" || thread.session?.status === "interrupted") {
38
32
  return true;
39
33
  }
@@ -45,16 +39,16 @@ export function isThreadCompleteEnough(thread: ThreadDetail) {
45
39
  }
46
40
 
47
41
  export function applyThreadEvent(
48
- current: ThreadDetail,
49
- event: ThreadEvent,
50
- messages: Map<string, ThreadMessage>,
42
+ current: OrchestrationThread,
43
+ event: OrchestrationEvent,
44
+ messages: Map<string, OrchestrationMessageType>,
51
45
  ) {
52
46
  const message = messageFromEvent(event, messages);
53
47
  if (message !== null) {
54
48
  messages.set(messageKey(message), message);
55
49
  return { ...current, messages: [...messages.values()] };
56
50
  }
57
- if (isThreadSessionSetEvent(event)) {
51
+ if (event.type === "thread.session-set") {
58
52
  return {
59
53
  ...current,
60
54
  session: event.payload.session,
@@ -64,32 +58,32 @@ export function applyThreadEvent(
64
58
  }
65
59
 
66
60
  export function messageFromEvent(
67
- event: ThreadEvent,
68
- existingMessages: Map<string, ThreadMessage> = new Map(),
69
- ): ThreadMessage | null {
70
- if (!isThreadMessageSentEvent(event)) {
61
+ event: OrchestrationEvent,
62
+ existingMessages: Map<string, OrchestrationMessageType> = new Map(),
63
+ ): OrchestrationMessageType | null {
64
+ if (event.type !== "thread.message-sent") {
71
65
  return null;
72
66
  }
73
67
  const payload = event.payload;
74
68
  const id = payload.messageId;
75
69
  const previous = existingMessages.get(id);
76
70
  const text = payload.text;
77
- return Schema.decodeUnknownSync(ThreadMessageSchema)({
71
+ return Schema.decodeUnknownSync(OrchestrationMessage)({
78
72
  id,
79
73
  role: payload.role,
80
74
  text: text.length > 0 || previous === undefined ? text : previous.text,
81
75
  turnId: payload.turnId,
82
- ...(payload.streaming !== undefined ? { streaming: payload.streaming } : {}),
76
+ streaming: payload.streaming,
83
77
  createdAt: payload.createdAt,
84
78
  updatedAt: payload.updatedAt,
85
79
  });
86
80
  }
87
81
 
88
- export function messageKey(message: ThreadMessage) {
89
- return message.id ?? message.messageId ?? `${message.role}:${message.createdAt}`;
82
+ export function messageKey(message: OrchestrationMessageType) {
83
+ return message.id;
90
84
  }
91
85
 
92
- function isPendingStart(thread: ThreadShell | ThreadDetail) {
86
+ function isPendingStart(thread: OrchestrationThreadShell | OrchestrationThread) {
93
87
  if (thread.session !== null || thread.latestTurn !== null || !("messages" in thread)) {
94
88
  return false;
95
89
  }
package/src/index.ts CHANGED
@@ -7,12 +7,12 @@ export type {
7
7
  } from "./application/service.ts";
8
8
  export type { ApplicationError } from "./application/error.ts";
9
9
  export type {
10
- ProjectShell,
10
+ OrchestrationMessage,
11
+ OrchestrationProjectShell,
12
+ OrchestrationShellSnapshot,
13
+ OrchestrationThread,
14
+ OrchestrationThreadShell,
11
15
  ServerProvider,
12
- ShellSnapshot,
13
- ThreadDetail,
14
- ThreadMessage,
15
- ThreadShell,
16
- } from "./domain/schema.ts";
16
+ } from "@t3tools/contracts";
17
17
  export { NodeEnvironmentLive } from "./environment/layer.ts";
18
18
  export { AppLayer, AuthAppLayer } from "./runtime.ts";