Last active
August 12, 2023 12:10
-
-
Save tarasglek/b72893346aa374b92cb2e38e5cfe213d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const name = "webrtc_shell_cmd"; | |
export const description = "This executes shell commands over webrtc connection. It's not able to execute interactive commands like vim, prefer to use tee, etc instead"; | |
/** | |
* 3. A JSON Schema defining the function's parameters. See: | |
* | |
* - https://platform.openai.com/docs/guides/gpt/function-calling | |
* - https://json-schema.org/learn/getting-started-step-by-step | |
*/ | |
export const parameters = { | |
type: "object", | |
properties: { | |
cmd: { | |
type: "string", | |
description: "Valid shell command", | |
} | |
}, | |
required: ["cmd"], | |
}; | |
class RTC { | |
pc; | |
dc; | |
_onMessage_cb = null; | |
_onConnected_cb = null; | |
constructor() { | |
this.pc = null; | |
this.dc = null; | |
} | |
onMessage(msg) { | |
if (this._onMessage_cb) { | |
this._onMessage_cb(msg); | |
} | |
this._onMessage_cb = null; | |
} | |
get connected() { | |
return this.pc && this.pc.connectionState == "connected"; | |
} | |
async connectAndcreateOffer() { | |
const config = { | |
iceServers: [ | |
{ | |
urls: "stun:stun.1.google.com:19302", | |
}, | |
], | |
}; | |
this.pc = new RTCPeerConnection(config); | |
this.dc = this.pc.createDataChannel("chat", { | |
negotiated: true, | |
id: 0, | |
}); | |
const dc = this.dc; | |
const pc = this.pc; | |
let self = this; | |
// pc.oniceconnectionstatechange = (ev) => handleChange(pc); | |
// pc.onconnectionstatechange = (ev) => handleChange(pc) | |
dc.onmessage = (ev) => self.onMessage(ev.data); | |
dc.onopen = (ev) => { | |
if (self._onConnected_cb) { | |
self._onConnected_cb(); | |
} | |
self._onConnected_cb = null; | |
}; | |
dc.onerror = (ev) => console.log(`dc.onerror() ${ev}`); | |
await pc.setLocalDescription(await pc.createOffer()); | |
return new Promise((resolve, reject) => { | |
pc.onicecandidate = ({ candidate }) => { | |
// console.log(`[createOffer()] onicecandidate() signalingState: ${this.pc.signalingState} candidate: ${candidate}`); | |
if (candidate) | |
return; | |
const offer = JSON.stringify(pc.localDescription); | |
resolve(offer); | |
}; | |
}); | |
} | |
async handleInput(input) { | |
console.log(`[handleInput()] input: ${input}`); | |
if (!this.pc) { | |
return await this.connectAndcreateOffer(); | |
} | |
else if (this.pc.connectionState != "connected") { | |
console.log(`[pc.connectionState == "${this.pc.connectionState}"]`); | |
// this is js code for handling case where offer first arrives from elsewhere | |
// if (this.pc.signalingState == "stable") { | |
// let offer = JSON.parse(input); | |
// console.log(`[pc.connectionState == "new"] prior to setRemoteDescription() signalingState: ${this.pc.signalingState}`); | |
// await this.pc.setRemoteDescription(offer); | |
// console.log(`[pc.connectionState == "new"] prior to setLocalDescription() signalingState: ${this.pc.signalingState}`); | |
// await this.pc.setLocalDescription(await this.pc.createAnswer()); | |
// console.log(`setLocalDescription`); | |
// const ret = new Promise((resolve, reject) => { | |
// this.pc.onicecandidate = ({ candidate }) => { | |
// console.log(`[pc.connectionState == "new"] onicecandidate() signalingState: ${this.pc.signalingState} candidate: ${candidate}`); | |
// if (candidate) return; | |
// const answer = JSON.stringify(this.pc.localDescription); | |
// resolve(answer); | |
// }; | |
// }); | |
// return await ret; | |
// } | |
// here expecting input to be JSON offer reply from other node | |
// receive counter offer, and try to connect | |
// if (this.pc.signalingState == "have-local-offer") | |
{ | |
const answer = JSON.parse(input); | |
let self = this; | |
const ret = new Promise((resolve, reject) => { | |
this._onConnected_cb = () => { | |
resolve("connected"); | |
}; | |
this.pc.setRemoteDescription(new RTCSessionDescription(answer)); | |
}); | |
return await ret; | |
} | |
} | |
// if we got here we are connected | |
// send message | |
// wait for response | |
const retP = new Promise((resolve, reject) => { | |
this.dc.send(input); | |
this._onMessage_cb = resolve; | |
}); | |
let ret = await retP; | |
console.log(`[handleInput()] ret: ${ret}`); | |
return ret; | |
} | |
} | |
export default async function (value) { | |
console.log(value); | |
const cmd = value.cmd; | |
// cache the connection | |
let rtc = window.rtc; | |
if (!rtc) { | |
window.rtc = rtc = new RTC(); | |
const offer = await rtc.handleInput(""); | |
let reply = prompt("Copy this offer to other node, and paste reply", offer); | |
console.log("reply", reply); | |
const replyReply = await rtc.handleInput(reply); | |
console.log(`rtc.connected: ${rtc.connected}`); | |
if (!rtc.connected) { | |
return replyReply; | |
} | |
return await rtc.handleInput(cmd); | |
} | |
const replyStr = await rtc.handleInput(cmd); | |
let output = undefined; | |
try { | |
const reply = JSON.parse(replyStr); | |
if (reply.error) { | |
return replyStr; | |
} | |
output = reply.output.trim(); | |
} | |
catch (e) { | |
} | |
return '```\n' + (output ? output : replyStr) + '\n```'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment