Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/desktop-app/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Logs
logs
storybook-static
*.log
npm-debug.log*
yarn-debug.log*
Expand Down
5 changes: 3 additions & 2 deletions cli/golem-cli/ts_bridge/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ export type GolemServer =

export type AroundInvokeHook = {
beforeInvoke: (request: AgentInvocationRequest) => Promise<void>;
afterInvoke: (request: AgentInvocationRequest, result: JsonResult<AgentInvocationResult, any>) => Promise<void>;
};
afterInvoke: (request: AgentInvocationRequest, result: JsonResult<AgentInvocationResult, unknown>) => Promise<void>;
}
;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this format look off (also this code will be soon moved to the sdk)


export type Configuration = {
server: GolemServer,
Expand Down
4,204 changes: 2,102 additions & 2,102 deletions sdks/ts/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions sdks/ts/packages/golem-ts-repl/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default [
'no-dupe-keys': 'error',
'no-duplicate-case': 'error',
eqeqeq: ['error', 'always'],
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
},
Expand Down
48 changes: 47 additions & 1 deletion sdks/ts/packages/golem-ts-repl/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export type AroundInvokeHook = {
beforeInvoke: (request: AgentInvocationRequest) => Promise<void>;
afterInvoke: (
request: AgentInvocationRequest,
result: JsonResult<AgentInvocationResult, any>,
result: JsonResult<AgentInvocationResult, unknown>,
) => Promise<void>;
};

Expand All @@ -41,6 +41,52 @@ export type EnvironmentName = string;
export type AgentTypeName = string;
export type IdempotencyKey = string;

export type Timestamp = string; // ISO 8601

export type WorkerStatus =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are defined in WIT so the binding generator (of wasm-rquickjs) should generate good types for them. If not, can we improve there?

To decide that please explain why you defined new ones

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I searched for worker definitions but didn't find one.And the old implementation just had worker name

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the original "mapping" is very lightweight intenionally, and just for looking up agent ids for completion from CLI output. we do have plans (and will soon have tickets) around exploring more typed integration with the CLI, so i see no point on having these here for now, especially that we currently keep changing APIs / CLI interfaces as part of the more agent focused APIs

| 'Running'
| 'Idle'
| 'Suspended'
| 'Interrupted'
| 'Retrying'
| 'Failed'
| 'Exited';

export type UpdateRecord =
| { type: 'PendingUpdate'; timestamp: Timestamp; targetRevision: number }
| { type: 'SuccessfulUpdate'; timestamp: Timestamp; targetRevision: number }
| {
type: 'FailedUpdate';
timestamp: Timestamp;
targetRevision: number;
details?: string;
};

export type WorkerResourceDescription = {
createdAt: Timestamp;
resourceOwner: string;
resourceName: string;
};

export type Worker = {
componentName: string;
workerName: string;
createdBy: string;
environmentId: string;
env: Record<string, string>;
configVars: Record<string, string>;
status: WorkerStatus;
componentRevision: number;
retryCount: number;
pendingInvocationCount: number;
updates: UpdateRecord[];
createdAt: Timestamp;
lastError?: string;
componentSize: number;
totalLinearMemorySize: number;
exportedResourceInstances: Record<string, WorkerResourceDescription>;
};

export type DataValue =
| { type: 'Tuple'; elements: ElementValue[] }
| { type: 'Multimodal'; elements: NamedElementValue[] };
Expand Down
24 changes: 14 additions & 10 deletions sdks/ts/packages/golem-ts-repl/src/cli-repl-interop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
// limitations under the License.

import childProcess, { ChildProcess } from 'node:child_process';
import { Buffer } from 'buffer';
import repl from 'node:repl';
import pc from 'picocolors';
import { CliArgMetadata, CliCommandMetadata, CliCommandsConfig } from './config';
import { flushStdIO, writeChunk } from './process';
import { writeFullLineSeparator } from './format';
import * as base from './base';
import { Worker } from './base';
import * as uuid from 'uuid';

const AGENT_STREAM_CLOSE_DELAY_MS = 100;
Expand Down Expand Up @@ -335,14 +337,15 @@ const COMPLETION_HOOKS: Partial<Record<CompletionHookId, CompletionHook>> = {
AGENT_ID: {
complete: async (cli, currentToken) => {
const result = await cli.runJson({ args: ['agent', 'list'] });
const json = result.json as { workers?: Partial<Worker>[] };

if (!result.ok || !result.json || !Array.isArray(result.json.workers)) {
if (!result.ok || !json || !Array.isArray(json.workers)) {
return [];
}

const values = result.json.workers
.map((worker: any) => worker.workerName)
.filter((value: unknown): value is string => typeof value === 'string');
const values = json.workers
.map((worker) => worker.workerName)
.filter((value): value is string => typeof value === 'string');

return filterByPrefix(values, currentToken);
},
Expand All @@ -355,12 +358,13 @@ const COMPLETION_HOOKS: Partial<Record<CompletionHookId, CompletionHook>> = {
return [];
}

if (!result.json || !Array.isArray(result.json)) {
const json = result.json as { componentName: string }[];
if (!json || !Array.isArray(json)) {
return [];
}

const values = result.json
.map((component: any) => component?.componentName)
const values = json
.map((component) => component?.componentName)
.filter((value: unknown): value is string => typeof value === 'string');

return filterByPrefix(values, currentToken);
Expand Down Expand Up @@ -415,11 +419,11 @@ class GolemCli {
let stderr = '';

if (opts.mode === 'collect') {
child.stdout?.on('data', (chunk) => {
child.stdout?.on('data', (chunk: Buffer) => {
stdout += chunk.toString();
});

child.stderr?.on('data', (chunk) => {
child.stderr?.on('data', (chunk: Buffer) => {
stderr += chunk.toString();
});
}
Expand All @@ -432,7 +436,7 @@ class GolemCli {

async runJson(opts: {
args: string[];
}): Promise<{ ok: boolean; code: number | null; json: any }> {
}): Promise<{ ok: boolean; code: number | null; json: unknown }> {
const result = await this.run({ args: ['--format', 'json', ...opts.args], mode: 'collect' });
return { ok: result.ok, code: result.code, json: JSON.parse(result.stdout) };
}
Expand Down
8 changes: 6 additions & 2 deletions sdks/ts/packages/golem-ts-repl/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ export type ReplCliFlags = {
streamLogs: boolean;
};

export type AgentConfig = {
export type AgentModule = Record<string, unknown> & {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the bridge SDK package is not really a record of string, it is a package, the plumbing here does not really change type safety compared to before.

configure: ConfigureClient;
};

export type AgentConfig<T extends AgentModule = AgentModule> = {
clientPackageName: string;
clientPackageImportedName: string;
package: any;
package: T;
};

export type ConfigureClient = (config: base.Configuration) => void;
Expand Down
7 changes: 5 additions & 2 deletions sdks/ts/packages/golem-ts-repl/src/language-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -668,8 +668,11 @@ function getFallbackArgumentInfo(
function getResolvedSignature(
callExpression: tsm.CallExpression | tsm.NewExpression,
): tsm.Signature | undefined {
const directSignature = (callExpression as any).getSignature?.();
if (directSignature) return directSignature;
const signature = callExpression
.getProject()
.getTypeChecker()
.getResolvedSignature(callExpression);
if (signature) return signature;

const isNew = callExpression.getKind() === tsm.SyntaxKind.NewExpression;
const expressionType = callExpression.getExpression().getType();
Expand Down
51 changes: 27 additions & 24 deletions sdks/ts/packages/golem-ts-repl/src/repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
cliCommandsConfigFromBaseConfig,
clientConfigFromEnv,
Config,
ConfigureClient,
loadReplCliFlags,
ReplCliFlags,
} from './config';
Expand All @@ -25,13 +24,13 @@ import { LanguageService } from './language-service';
import pc from 'picocolors';
import repl, { type REPLEval } from 'node:repl';
import process from 'node:process';
import { AsyncCompleter } from 'readline';
import { Completer, AsyncCompleter } from 'readline';
import { PassThrough } from 'node:stream';
import { ts } from 'ts-morph';
import { flushStdIO, setOutput, writeln } from './process';
import { formatAsTable, formatEvalError, logSnippetInfo } from './format';
import * as base from './base';
import { AgentInvocationRequest, AgentInvocationResult, JsonResult } from './base';
import { AgentInvocationRequest } from './base';

const MAX_COMPLETION_ENTRIES = 50;

Expand Down Expand Up @@ -61,11 +60,7 @@ export class Repl {
}
},

async afterInvoke(
request: AgentInvocationRequest,
result: JsonResult<AgentInvocationResult, any>,
): Promise<void> {
void result;
async afterInvoke(request: AgentInvocationRequest): Promise<void> {
await cli.stopAgentStream(request);
},
};
Expand All @@ -80,11 +75,13 @@ export class Repl {

private newBaseReplServer(options?: {
input?: NodeJS.ReadableStream;
output?: NodeJS.WritableStream;
output?: NodeJS.WriteStream;
terminal?: boolean;
eval?: REPLEval;
completer?: Completer | AsyncCompleter;
}): repl.REPLServer {
const output = options?.output ?? process.stdout;
const terminal = options?.terminal ?? Boolean((output as any).isTTY);
const terminal = options?.terminal ?? Boolean(output.isTTY);
const prompt = this.replCliFlags.script
? ''
: `${pc.cyan('golem-ts-repl')}` +
Expand All @@ -101,13 +98,13 @@ export class Repl {
preview: false,
ignoreUndefined: true,
prompt,
eval: options?.eval,
completer: options?.completer,
});
}

private async setupRepl(replServer: repl.REPLServer): Promise<void> {
await this.setupReplHistory(replServer);
this.setupReplEval(replServer);
this.setupReplCompleter(replServer);
this.setupReplContext(replServer);
this.setupReplCommands(replServer);
}
Expand All @@ -124,13 +121,12 @@ export class Repl {
});
}

private setupReplEval(replServer: repl.REPLServer): repl.REPLEval {
const tsxEval = replServer.eval;
private createCustomEval(tsxEval: REPLEval): REPLEval {
const languageService = this.getLanguageService();
const replCliFlags = this.replCliFlags;
const getOverrideSnippet = () => this.overrideSnippetForNextEval;

const customEval: REPLEval = function (code, context, filename, callback) {
return function (this: repl.REPLServer, code, context, filename, callback) {
const evalCode = (code: string) => {
tsxEval.call(this, code, context, filename, (err, result) => {
if (!err) {
Expand Down Expand Up @@ -179,16 +175,12 @@ export class Repl {
callback(null, undefined);
}
};
(replServer.eval as any) = customEval;

return tsxEval;
}

private setupReplCompleter(replServer: repl.REPLServer) {
const nodeCompleter = replServer.completer;
private createCustomCompleter(nodeCompleter: Completer | AsyncCompleter): AsyncCompleter {
const languageService = this.getLanguageService();
const cli = this.cli;
const customCompleter: AsyncCompleter = function (line, callback) {
return function (line, callback) {
if (line.trimStart().startsWith('.')) {
cli
.complete(line)
Expand Down Expand Up @@ -216,7 +208,6 @@ export class Repl {
}
}
};
(replServer.completer as any) = customCompleter;
}

private setupReplContext(replServer: repl.REPLServer) {
Expand All @@ -227,7 +218,7 @@ export class Repl {
const context = replServer.context;
for (let agentTypeName in this.config.agents) {
const agentConfig = this.config.agents[agentTypeName];
let configure = agentConfig.package.configure as ConfigureClient;
let configure = agentConfig.package.configure;
configure(this.clientConfig);
context[agentTypeName] = agentConfig.package[agentTypeName];
context[agentConfig.clientPackageImportedName] = agentConfig.package;
Expand Down Expand Up @@ -346,13 +337,25 @@ export class Repl {
setOutput('stdout');

const script = this.replCliFlags.script;

// We need to create a temporary repl server to get the default eval and completer
Copy link
Contributor

@noise64 noise64 Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pactching of the repl was intenionally used the actual instance. (And there is another patching in place before, that is created by tsx, and we follow the way). Not sure it this breaks anything, but there is no point in changing the current solution.

const tempRepl = repl.start({ input: new PassThrough(), output: new PassThrough() });
const customEval = this.createCustomEval(tempRepl.eval);
const customCompleter = this.createCustomCompleter(tempRepl.completer);
tempRepl.close();

const replServer = script
? this.newBaseReplServer({
input: new PassThrough(),
output: process.stdout,
terminal: false,
eval: customEval,
completer: customCompleter,
})
: this.newBaseReplServer();
: this.newBaseReplServer({
eval: customEval,
completer: customCompleter,
});

await this.setupRepl(replServer);

Expand Down
1 change: 1 addition & 0 deletions sdks/ts/packages/golem-ts-sdk/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default [
'no-dupe-keys': 'error',
'no-duplicate-case': 'error',
eqeqeq: ['error', 'always'],
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
},
Expand Down
Loading
Loading