WIP Stash
This commit is contained in:
1 parent
9585008433
commit
d934dc0ef7
9 files changed
+175
-78
No files matched your search
@@ -5,7 +5,7 @@ sqlalchemy
|
|||||||
nest_asyncio
|
nest_asyncio
|
||||||
replicate
|
replicate
|
||||||
qdrant-client[fastembed]
|
qdrant-client[fastembed]
|
||||||
ollama
|
#ollama
|
||||||
tortoise-orm>=1.0.0
|
tortoise-orm>=1.0.0
|
||||||
llama-cpp-python
|
#llama-cpp-python
|
||||||
openrouter
|
openrouter
|
||||||
+17
-22
@@ -2,6 +2,7 @@ import re
|
|||||||
import inspect
|
import inspect
|
||||||
import time
|
import time
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import traceback
|
||||||
|
|
||||||
from temporary import generate_tmp_id
|
from temporary import generate_tmp_id
|
||||||
from stream import Stream
|
from stream import Stream
|
||||||
@@ -66,7 +67,18 @@ class Agent:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
await self.run(run)
|
await self.run(run)
|
||||||
return run.get_output_stream()
|
out_stream = Stream(self.app)
|
||||||
|
|
||||||
|
def on_output_event(stream):
|
||||||
|
def on_chunk(chunk):
|
||||||
|
out_stream.feed(chunk)
|
||||||
|
stream.listen(on_chunk, lambda: None)
|
||||||
|
def on_done(data):
|
||||||
|
out_stream.close()
|
||||||
|
run.on('output', on_output_event)
|
||||||
|
run.on('done', on_done)
|
||||||
|
|
||||||
|
return out_stream
|
||||||
|
|
||||||
async def received_message(self, message):
|
async def received_message(self, message):
|
||||||
chat_view = await self.app.get_chat_view(message.chat_id)
|
chat_view = await self.app.get_chat_view(message.chat_id)
|
||||||
@@ -213,7 +225,8 @@ class AgentRun:
|
|||||||
cb(data)
|
cb(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR: handling event '{event_name}' in run {self.id}\n" + str(e))
|
print(f"ERROR: handling event '{event_name}' in run {self.id}\n" + str(e))
|
||||||
|
print(traceback.format_exc())
|
||||||
|
|
||||||
# Broadcast event to websocket listeners
|
# Broadcast event to websocket listeners
|
||||||
self.app.emit_event("run-event", {
|
self.app.emit_event("run-event", {
|
||||||
"runId": self.id,
|
"runId": self.id,
|
||||||
@@ -221,24 +234,6 @@ class AgentRun:
|
|||||||
"data": data
|
"data": data
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_output_stream(self):
|
|
||||||
if not self.output:
|
|
||||||
self.output = Stream(self.app)
|
|
||||||
self.emit_event('output', self.output)
|
|
||||||
return self.output
|
|
||||||
|
|
||||||
def feed_output(self, chunk):
|
|
||||||
stream = self.get_output_stream()
|
|
||||||
if chunk:
|
|
||||||
self.output.feed(chunk)
|
|
||||||
|
|
||||||
def feed_reasoning(self, chunk):
|
|
||||||
if not self.reasoning:
|
|
||||||
self.reasoning = Stream(self.app)
|
|
||||||
self.emit_event('reasoning', self.reasoning)
|
|
||||||
if chunk:
|
|
||||||
self.reasoning.feed(chunk)
|
|
||||||
|
|
||||||
def add_tools(self, tools):
|
def add_tools(self, tools):
|
||||||
if "tools" in self.input:
|
if "tools" in self.input:
|
||||||
self.input["tools"] += tools
|
self.input["tools"] += tools
|
||||||
@@ -298,7 +293,7 @@ class AgentRun:
|
|||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
# Emit cancellation event
|
# Emit cancellation event
|
||||||
self.emit_event("cancelled", {"runId": self.id, "agentId": self.agent_id})
|
self.emit_event("cancelled", {})
|
||||||
|
|
||||||
# Broadcast cancellation event
|
# Broadcast cancellation event
|
||||||
self.app.emit_event("run-cancelled", {
|
self.app.emit_event("run-cancelled", {
|
||||||
@@ -314,4 +309,4 @@ class AgentRun:
|
|||||||
self.end_time = int(time.time())
|
self.end_time = int(time.time())
|
||||||
if self.output: self.output.close()
|
if self.output: self.output.close()
|
||||||
if self.reasoning: self.reasoning.close()
|
if self.reasoning: self.reasoning.close()
|
||||||
self.emit_event("done", {"output": self.output.content, "agent_id": self.agent_id})
|
self.emit_event("done", {})
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import inference
|
import inference
|
||||||
from agent import Agent, AgentField
|
from agent import Agent, AgentField
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
CLASS_DESCRIPTION = """
|
CLASS_DESCRIPTION = """
|
||||||
Expert AI with customizable system prompt.
|
Expert AI with customizable system prompt.
|
||||||
Will get guidelines, documents, and other advanced features in the future.
|
Will get guidelines, documents, and other advanced features in the future.
|
||||||
@@ -22,16 +24,18 @@ class ExpertAgent(Agent):
|
|||||||
async def run(self, run):
|
async def run(self, run):
|
||||||
opts = {}
|
opts = {}
|
||||||
|
|
||||||
agent_tools = self.tools
|
using_tools = False
|
||||||
print('agent tools ' + repr(agent_tools))
|
if 'tools' not in run.input:
|
||||||
run.add_tools(agent_tools)
|
if self.tools:
|
||||||
|
run.input['tools'] = self.tools
|
||||||
|
using_tools = True
|
||||||
|
|
||||||
if self.config.get("temperature") is not None:
|
if self.config.get("temperature") is not None:
|
||||||
opts["temperature"] = self.config["temperature"]
|
opts["temperature"] = self.config["temperature"]
|
||||||
if self.config.get("repetition_penalty") is not None:
|
if self.config.get("repetition_penalty") is not None:
|
||||||
opts["repetition_penalty"] = self.config["repetition_penalty"]
|
opts["repetition_penalty"] = self.config["repetition_penalty"]
|
||||||
|
|
||||||
# 1. Build message list based on input
|
# Build message list based on input
|
||||||
messages = inference.build_messages(
|
messages = inference.build_messages(
|
||||||
system_prompt=self.config.get("system_prompt", ""),
|
system_prompt=self.config.get("system_prompt", ""),
|
||||||
instruction=run.input.get("instruction"),
|
instruction=run.input.get("instruction"),
|
||||||
@@ -39,14 +43,44 @@ class ExpertAgent(Agent):
|
|||||||
messages=run.input.get("messages"),
|
messages=run.input.get("messages"),
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. Call inference, passing the run to feed run.stream and tool calls
|
# Collect inference output. These are resused across calls
|
||||||
await inference.chat_completion(
|
tool_calls = []
|
||||||
self.app,
|
output_stream = None
|
||||||
model_id=self.config.get("model"),
|
inference_end = asyncio.Event()
|
||||||
messages=messages,
|
def on_output(stream):
|
||||||
tools=run.input.get("tools", []),
|
nonlocal output_stream
|
||||||
opts=opts,
|
output_stream = stream
|
||||||
agent_run=run,
|
run.on('output', on_output)
|
||||||
)
|
run.on('inference-end', lambda event: inference_end.set())
|
||||||
|
run.on('tool-call', lambda tc: tool_calls.append(tc))
|
||||||
|
|
||||||
run.get_output_stream().listen(lambda chunk: None, lambda: run.close())
|
while True:
|
||||||
|
# Reset data
|
||||||
|
inference_end.clear()
|
||||||
|
tool_calls = []
|
||||||
|
output_stream = None
|
||||||
|
|
||||||
|
# Call inference, passing the run to get events out
|
||||||
|
await inference.chat_completion(
|
||||||
|
self.app,
|
||||||
|
model_id=self.config.get("model"),
|
||||||
|
messages=messages,
|
||||||
|
tools=run.input.get("tools", []),
|
||||||
|
opts=opts,
|
||||||
|
agent_run=run,
|
||||||
|
)
|
||||||
|
|
||||||
|
await inference_end.wait()
|
||||||
|
|
||||||
|
messages.append(inference.LlamaMessage('assistant', output_stream and await output_stream.content_wait() or ""))
|
||||||
|
|
||||||
|
if using_tools and tool_calls:
|
||||||
|
for tc in tool_calls:
|
||||||
|
tool = next(t for t in self.tools if t.name == tc.tool_name)
|
||||||
|
if not tool:
|
||||||
|
print(f"Bad agent tool name: {tc.name}")
|
||||||
|
continue
|
||||||
|
tc.result = await tool.execute_async(**tc.arguments)
|
||||||
|
messages.append(inference.LlamaMessage('tool', tc))
|
||||||
|
else:
|
||||||
|
break
|
||||||
+102
-34
@@ -2,12 +2,12 @@ import threading
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import ollama
|
#import ollama
|
||||||
import traceback
|
import traceback
|
||||||
from stream import Stream
|
from stream import Stream
|
||||||
from tool import format_tools_for_engine, build_tool_prompt_prefix, ToolSpec, ToolCall
|
from tool import format_tools_for_engine, build_tool_prompt_prefix, ToolSpec, ToolCall
|
||||||
|
|
||||||
from llama_cpp import Llama
|
#from llama_cpp import Llama
|
||||||
from openrouter import OpenRouter
|
from openrouter import OpenRouter
|
||||||
|
|
||||||
VALID_ROLES = ['user', 'assistant', 'system', 'tool']
|
VALID_ROLES = ['user', 'assistant', 'system', 'tool']
|
||||||
@@ -35,12 +35,60 @@ class Models:
|
|||||||
qwen3_8b = "lucataco/qwen3-vl-8b-instruct:39e893666996acf464cff75688ad49ac95ef54e9f1c688fbc677330acc478e11"
|
qwen3_8b = "lucataco/qwen3-vl-8b-instruct:39e893666996acf464cff75688ad49ac95ef54e9f1c688fbc677330acc478e11"
|
||||||
qwen3_235b = "qwen/qwen3-235b-a22b-instruct-2507"
|
qwen3_235b = "qwen/qwen3-235b-a22b-instruct-2507"
|
||||||
|
|
||||||
|
def format_openai(messages):
|
||||||
|
formatted = []
|
||||||
|
last_assistant_message = None
|
||||||
|
for msg in messages:
|
||||||
|
if msg.role == 'tool':
|
||||||
|
if not last_assistant_message:
|
||||||
|
raise Exception("tool message before any assistant message")
|
||||||
|
tool_calls = last_assistant_message.get('tool_calls')
|
||||||
|
if tool_calls == None:
|
||||||
|
tool_calls = []
|
||||||
|
last_assistant_message['tool_calls'] = tool_calls
|
||||||
|
tool_calls.append({
|
||||||
|
'id': msg.content.id,
|
||||||
|
'type': 'function',
|
||||||
|
'function': {
|
||||||
|
'name': msg.content.tool_name,
|
||||||
|
'arguments': json.dumps(msg.content.arguments),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
formatted.append({
|
||||||
|
'role': 'tool',
|
||||||
|
'tool_call_id': msg.content.id,
|
||||||
|
'content': json.dumps(msg.content.output),
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
fmsg = {'role': msg.role, 'content': msg.content}
|
||||||
|
formatted.append(fmsg)
|
||||||
|
if msg.role == 'assistant':
|
||||||
|
last_assistant_message = fmsg
|
||||||
|
return formatted
|
||||||
|
|
||||||
|
|
||||||
# llama cpp models
|
# llama cpp models
|
||||||
model_cache = {}
|
model_cache = {}
|
||||||
last_model_file = None
|
last_model_file = None
|
||||||
last_model_instance = None
|
last_model_instance = None
|
||||||
|
|
||||||
|
def safe_queue(callback):
|
||||||
|
queue = asyncio.Queue()
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
async def read_loop():
|
||||||
|
while True:
|
||||||
|
args = await queue.get()
|
||||||
|
await callback(*args)
|
||||||
|
loop.create_task(read_loop())
|
||||||
|
|
||||||
|
def send(*args):
|
||||||
|
def queue_put():
|
||||||
|
loop.create_task(queue.put(args))
|
||||||
|
loop.call_soon_threadsafe(queue_put)
|
||||||
|
return send
|
||||||
|
|
||||||
def get_llama_cpp_model(app, model_file):
|
def get_llama_cpp_model(app, model_file):
|
||||||
|
raise Exception("llama-cpp disabled")
|
||||||
global last_model_file, last_model_instance
|
global last_model_file, last_model_instance
|
||||||
if last_model_file == model_file:
|
if last_model_file == model_file:
|
||||||
return last_model_instance
|
return last_model_instance
|
||||||
@@ -95,24 +143,13 @@ def build_messages(system_prompt="", instruction=None, input=None, messages=None
|
|||||||
|
|
||||||
async def chat_completion(app,
|
async def chat_completion(app,
|
||||||
model_id=None,
|
model_id=None,
|
||||||
instruction=None,
|
|
||||||
input=None,
|
|
||||||
messages=None,
|
messages=None,
|
||||||
system_prompt="",
|
|
||||||
tools=[],
|
tools=[],
|
||||||
opts={},
|
opts={},
|
||||||
agent_run=None
|
agent_run=None
|
||||||
):
|
):
|
||||||
db_model = (await app.get_model(model_id)).value
|
db_model = (await app.get_model(model_id)).value
|
||||||
|
|
||||||
# Integrate arguments into message list
|
|
||||||
messages = build_messages(
|
|
||||||
system_prompt=system_prompt,
|
|
||||||
instruction=instruction,
|
|
||||||
input=input,
|
|
||||||
messages=messages,
|
|
||||||
)
|
|
||||||
|
|
||||||
model_engine = db_model['engine']
|
model_engine = db_model['engine']
|
||||||
model_name = db_model['model']
|
model_name = db_model['model']
|
||||||
|
|
||||||
@@ -140,10 +177,22 @@ async def chat_completion(app,
|
|||||||
|
|
||||||
# llama.cpp
|
# llama.cpp
|
||||||
async def local_chat_completion(app, messages, model, tools=[], opts={}, agent_run=None):
|
async def local_chat_completion(app, messages, model, tools=[], opts={}, agent_run=None):
|
||||||
if agent_run:
|
if not agent_run:
|
||||||
content_stream = agent_run.get_output_stream()
|
raise Exception("agent_run required")
|
||||||
else:
|
|
||||||
content_stream = Stream(app)
|
content_stream = None
|
||||||
|
async def message_loop(name, value=None):
|
||||||
|
nonlocal content_stream, agent_run
|
||||||
|
if name == 'output':
|
||||||
|
if content_stream == None:
|
||||||
|
content_stream = Stream(app)
|
||||||
|
agent_run.emit_event('output', content_stream)
|
||||||
|
content_stream.feed(value)
|
||||||
|
elif name == 'close':
|
||||||
|
if content_stream:
|
||||||
|
content_stream.close()
|
||||||
|
agent_run.emit_event('inference-end', {})
|
||||||
|
send_message = safe_queue(message_loop)
|
||||||
|
|
||||||
def worker():
|
def worker():
|
||||||
llm = get_llama_cpp_model(app, model)
|
llm = get_llama_cpp_model(app, model)
|
||||||
@@ -179,21 +228,16 @@ async def local_chat_completion(app, messages, model, tools=[], opts={}, agent_r
|
|||||||
|
|
||||||
delta = chunk['choices'][0]['delta']
|
delta = chunk['choices'][0]['delta']
|
||||||
if 'content' in delta:
|
if 'content' in delta:
|
||||||
chunk_text = delta['content']
|
send_message('output', delta['content'])
|
||||||
content_stream.feed(chunk_text)
|
|
||||||
# Handle tool_call deltas if the model emits them
|
# Handle tool_call deltas if the model emits them
|
||||||
if 'tool_calls' in delta:
|
if 'tool_calls' in delta:
|
||||||
# Forward tool call chunks as JSON strings for the consumer
|
# Forward tool call chunks as JSON strings for the consumer
|
||||||
for tc in delta['tool_calls']:
|
send_message("tool-call", {
|
||||||
content_stream.feed(json.dumps({'tool_call': tc}))
|
"id": tc.get("id"),
|
||||||
if agent_run:
|
"function": tc.get("function"),
|
||||||
for tc in delta['tool_calls']:
|
})
|
||||||
agent_run.emit_event("tool_call", {
|
|
||||||
"id": tc.get("id"),
|
|
||||||
"function": tc.get("function"),
|
|
||||||
})
|
|
||||||
|
|
||||||
content_stream.close()
|
send_message('close')
|
||||||
|
|
||||||
thread = threading.Thread(target=worker)
|
thread = threading.Thread(target=worker)
|
||||||
if agent_run:
|
if agent_run:
|
||||||
@@ -209,6 +253,7 @@ async def ollama_list(app, messages, model, opts={}):
|
|||||||
return ollama.list()
|
return ollama.list()
|
||||||
|
|
||||||
async def ollama_chat_completion(app, messages, model, tools=[], opts={}, agent_run=None):
|
async def ollama_chat_completion(app, messages, model, tools=[], opts={}, agent_run=None):
|
||||||
|
raise Exception("ollama disabled")
|
||||||
if agent_run:
|
if agent_run:
|
||||||
content_stream = agent_run.stream
|
content_stream = agent_run.stream
|
||||||
else:
|
else:
|
||||||
@@ -259,6 +304,29 @@ async def openrouter_chat_completion(app, messages, model, tools=[], opts={}, ag
|
|||||||
if not agent_run:
|
if not agent_run:
|
||||||
raise Exception("agent_run required")
|
raise Exception("agent_run required")
|
||||||
|
|
||||||
|
# TODO: Separate as InferenceRun class
|
||||||
|
content_stream = None
|
||||||
|
reasoning_stream = None
|
||||||
|
async def message_loop(name, value=None):
|
||||||
|
nonlocal content_stream, reasoning_stream, agent_run
|
||||||
|
if name == 'output':
|
||||||
|
if content_stream == None:
|
||||||
|
content_stream = Stream(app)
|
||||||
|
agent_run.emit_event('output', content_stream)
|
||||||
|
content_stream.feed(value)
|
||||||
|
if name == 'reasoning':
|
||||||
|
if reasoning_stream == None:
|
||||||
|
reasoning_stream = Stream(app)
|
||||||
|
agent_run.emit_event('output', reasoning_stream)
|
||||||
|
reasoning_stream.feed(value)
|
||||||
|
if name == 'tool-call':
|
||||||
|
agent_run.emit_event('tool-call', value)
|
||||||
|
elif name == 'close':
|
||||||
|
if content_stream: content_stream.close()
|
||||||
|
if reasoning_stream: reasoning_stream.close()
|
||||||
|
agent_run.emit_event('inference-end', {})
|
||||||
|
emit = safe_queue(message_loop)
|
||||||
|
|
||||||
async def worker():
|
async def worker():
|
||||||
print("START OPENROUTER INFERENCE")
|
print("START OPENROUTER INFERENCE")
|
||||||
if not app.openrouter_client:
|
if not app.openrouter_client:
|
||||||
@@ -266,7 +334,7 @@ async def openrouter_chat_completion(app, messages, model, tools=[], opts={}, ag
|
|||||||
|
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'model': model,
|
'model': model,
|
||||||
'messages': [msg.to_dict() for msg in messages],
|
'messages': format_openai(messages),
|
||||||
'stream': True,
|
'stream': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,9 +362,9 @@ async def openrouter_chat_completion(app, messages, model, tools=[], opts={}, ag
|
|||||||
delta = event.choices[0].delta
|
delta = event.choices[0].delta
|
||||||
|
|
||||||
if delta.content:
|
if delta.content:
|
||||||
agent_run.feed_output(delta.content)
|
emit('output', delta.content)
|
||||||
if delta.reasoning:
|
if delta.reasoning:
|
||||||
agent_run.feed_reasoning(delta.reasoning)
|
emit('reasoning', delta.reasoning)
|
||||||
if delta.tool_calls:
|
if delta.tool_calls:
|
||||||
print("[openrouter] tool call:" + repr(delta.tool_calls))
|
print("[openrouter] tool call:" + repr(delta.tool_calls))
|
||||||
for tc in delta.tool_calls:
|
for tc in delta.tool_calls:
|
||||||
@@ -325,9 +393,9 @@ async def openrouter_chat_completion(app, messages, model, tools=[], opts={}, ag
|
|||||||
try:
|
try:
|
||||||
arguments = json.loads(tc.arguments)
|
arguments = json.loads(tc.arguments)
|
||||||
tc.arguments = arguments
|
tc.arguments = arguments
|
||||||
agent_run.emit_event('tool-call', tc)
|
emit('tool-call', tc)
|
||||||
except Exception:
|
except Exception:
|
||||||
print("[openrouter] Bad tool call arguments")
|
print("[openrouter] Bad tool call arguments for " + repr(tc))
|
||||||
print(traceback.format_exc())
|
print(traceback.format_exc())
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
print(f"[openrouter] task cancelled for run {agent_run.id}")
|
print(f"[openrouter] task cancelled for run {agent_run.id}")
|
||||||
@@ -336,7 +404,7 @@ async def openrouter_chat_completion(app, messages, model, tools=[], opts={}, ag
|
|||||||
print(f"[openrouter] exception during inference")
|
print(f"[openrouter] exception during inference")
|
||||||
print(traceback.format_exc())
|
print(traceback.format_exc())
|
||||||
|
|
||||||
agent_run.close()
|
emit('close')
|
||||||
print("END OPENROUTER INFERENCE")
|
print("END OPENROUTER INFERENCE")
|
||||||
|
|
||||||
task = asyncio.create_task(worker())
|
task = asyncio.create_task(worker())
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from tortoise import migrations
|
from tortoise import migrations
|
||||||
from tortoise.migrations import operations as ops
|
from tortoise.migrations import operations as ops
|
||||||
from orjson import loads
|
from json import loads
|
||||||
from tortoise.fields.base import OnDelete
|
from tortoise.fields.base import OnDelete
|
||||||
from tortoise.fields.data import JSON_DUMPS
|
from tortoise.fields.data import JSON_DUMPS
|
||||||
from tortoise import fields
|
from tortoise import fields
|
||||||
|
|||||||
@@ -587,4 +587,4 @@ async def start_server(app):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
server.run(debug=True)
|
await server.run_task(debug=True)
|
||||||
+3
-1
@@ -1,4 +1,5 @@
|
|||||||
import inspect
|
import inspect
|
||||||
|
import asyncio
|
||||||
from typing import get_type_hints
|
from typing import get_type_hints
|
||||||
|
|
||||||
class ToolParameter:
|
class ToolParameter:
|
||||||
@@ -225,7 +226,8 @@ class ToolCall:
|
|||||||
self.result = None
|
self.result = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"ToolCall(tool={self.tool_name!r}, args={self.arguments!r})"
|
id_prefix = self.id + ', ' if self.id else ''
|
||||||
|
return f"ToolCall({id_prefix}tool={self.tool_name!r}, args={self.arguments!r})"
|
||||||
|
|
||||||
def call_message(self):
|
def call_message(self):
|
||||||
raise Exception("TODO: Llama message for tool call")
|
raise Exception("TODO: Llama message for tool call")
|
||||||
|
|||||||
@@ -67,8 +67,6 @@ function updateRun(event) {
|
|||||||
}
|
}
|
||||||
} else if (name == 'done') {
|
} else if (name == 'done') {
|
||||||
run.done = true;
|
run.done = true;
|
||||||
run.endTime = data.endTime;
|
|
||||||
run.output = data.output;
|
|
||||||
} else if (name == 'chat-message') {
|
} else if (name == 'chat-message') {
|
||||||
let chatListeners = listeners.chats[data.chatId] || [];
|
let chatListeners = listeners.chats[data.chatId] || [];
|
||||||
for (let f of chatListeners) {
|
for (let f of chatListeners) {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function isStreamEvent(event) {
|
function isStreamEvent(event) {
|
||||||
return typeof event.data == "object" && event.data.streamId != null;
|
return event.data instanceof Object && event.data.streamId != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStreamRef(event) {
|
function getStreamRef(event) {
|
||||||
|
|||||||
Reference in new issue
Block a user